@aglyn/plugins-bookings 1.0.0-beta.221 → 1.0.0-beta.222

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +3 -2
  2. package/package.json +11 -11
  3. package/src/lib/components/book-meeting-action.js +5 -3
  4. package/src/lib/components/book-meeting-action.js.map +1 -1
  5. package/src/lib/components/booking.d.ts +33 -0
  6. package/src/lib/components/booking.js +260 -54
  7. package/src/lib/components/booking.js.map +1 -1
  8. package/src/lib/components/bookings-console-page.js +139 -15
  9. package/src/lib/components/bookings-console-page.js.map +1 -1
  10. package/src/lib/declarations.console-server.d.ts +34 -0
  11. package/src/lib/declarations.console-server.js +84 -0
  12. package/src/lib/declarations.console-server.js.map +1 -0
  13. package/src/lib/model/booking-contact-fields.d.ts +88 -0
  14. package/src/lib/model/booking-contact-fields.js +130 -0
  15. package/src/lib/model/booking-contact-fields.js.map +1 -0
  16. package/src/lib/model/booking-price.d.ts +51 -0
  17. package/src/lib/model/booking-price.js +61 -0
  18. package/src/lib/model/booking-price.js.map +1 -0
  19. package/src/lib/model/booking-record.d.ts +7 -0
  20. package/src/lib/model/booking-record.js +11 -2
  21. package/src/lib/model/booking-record.js.map +1 -1
  22. package/src/lib/model/bookings-record-counts.d.ts +25 -0
  23. package/src/lib/model/bookings-record-counts.js +34 -0
  24. package/src/lib/model/bookings-record-counts.js.map +1 -0
  25. package/src/lib/model/bookings.d.ts +61 -4
  26. package/src/lib/model/bookings.js +81 -15
  27. package/src/lib/model/bookings.js.map +1 -1
  28. package/src/lib/model/index.d.ts +2 -0
  29. package/src/lib/model/index.js +2 -0
  30. package/src/lib/model/index.js.map +1 -1
  31. package/src/lib/plugin.js +14 -0
  32. package/src/lib/plugin.js.map +1 -1
  33. package/src/lib/server/billing-webhook.js +33 -15
  34. package/src/lib/server/billing-webhook.js.map +1 -1
  35. package/src/lib/server/booking-crm.js +4 -1
  36. package/src/lib/server/booking-crm.js.map +1 -1
  37. package/src/lib/server/person-eraser.d.ts +36 -0
  38. package/src/lib/server/person-eraser.js +48 -0
  39. package/src/lib/server/person-eraser.js.map +1 -0
  40. package/src/lib/server/refund.js +2 -2
  41. package/src/lib/server/refund.js.map +1 -1
  42. package/src/lib/server.js +97 -36
  43. package/src/lib/server.js.map +1 -1
  44. package/src/lib/transfer/bookings-transfer-common.d.ts +68 -0
  45. package/src/lib/transfer/bookings-transfer-common.js +130 -0
  46. package/src/lib/transfer/bookings-transfer-common.js.map +1 -0
  47. package/src/lib/transfer/bookings-transfer.d.ts +86 -0
  48. package/src/lib/transfer/bookings-transfer.js +613 -0
  49. package/src/lib/transfer/bookings-transfer.js.map +1 -0
  50. package/src/lib/transfer/bookings-transfer.server.d.ts +26 -0
  51. package/src/lib/transfer/bookings-transfer.server.js +31 -0
  52. package/src/lib/transfer/bookings-transfer.server.js.map +1 -0
@@ -0,0 +1,613 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { TRANSFER_ID_FIELD, buildMatchLookup } from "@aglyn/aglyn/data-transfer";
18
+ import { zonedDateTime } from "@aglyn/shared-util-timestamp/zoned-time";
19
+ import { FieldPath } from "firebase-admin/firestore";
20
+ import { bookingTimeZone, storedBookingTimeZone } from "../model/booking-time.js";
21
+ import { BOOKINGS_TRANSFER_MATCH_KEYS, parseBookingsTransferFilter } from "./bookings-transfer-common.js";
22
+ /** The most rows one page answers when the engine does not say. */ const DEFAULT_PAGE_ROWS = 500;
23
+ const MAX_PAGE_ROWS = 1000;
24
+ /** Firestore's bound on an `in` list. */ const IN_LIMIT = 30;
25
+ /*==========================================
26
+ * THE FIELDS
27
+ *=========================================*/ export const BOOKINGS_TRANSFER_GROUPS = [
28
+ {
29
+ id: 'booking',
30
+ label: 'Booking'
31
+ },
32
+ {
33
+ id: 'customer',
34
+ label: 'Customer'
35
+ },
36
+ {
37
+ id: 'payment',
38
+ label: 'Payment'
39
+ },
40
+ {
41
+ id: 'system',
42
+ label: 'System'
43
+ }
44
+ ];
45
+ const readOnly = (field)=>_extends({}, field, {
46
+ readOnly: true
47
+ });
48
+ /** The booking's own stored fields. */ const STANDARD = [
49
+ {
50
+ id: 'serviceId',
51
+ label: 'Service ID',
52
+ group: 'booking',
53
+ type: 'text',
54
+ description: 'The service that was booked.'
55
+ },
56
+ {
57
+ id: 'serviceName',
58
+ label: 'Service',
59
+ group: 'booking',
60
+ type: 'text',
61
+ aliases: [
62
+ 'service name'
63
+ ],
64
+ description: 'The service’s name when it was booked.'
65
+ },
66
+ {
67
+ id: 'startsAt',
68
+ label: 'Starts (UTC)',
69
+ group: 'booking',
70
+ type: 'datetime',
71
+ aliases: [
72
+ 'start',
73
+ 'starts at',
74
+ 'start time'
75
+ ]
76
+ },
77
+ {
78
+ id: 'endsAt',
79
+ label: 'Ends (UTC)',
80
+ group: 'booking',
81
+ type: 'datetime',
82
+ aliases: [
83
+ 'end',
84
+ 'ends at',
85
+ 'end time'
86
+ ]
87
+ },
88
+ {
89
+ id: 'timeZone',
90
+ label: 'Time zone',
91
+ group: 'booking',
92
+ type: 'text',
93
+ aliases: [
94
+ 'timezone'
95
+ ],
96
+ description: 'The zone the slot was offered in. Bookings made before it was stored read it from the service, then the site.'
97
+ },
98
+ {
99
+ id: 'status',
100
+ label: 'Status',
101
+ group: 'booking',
102
+ type: 'text',
103
+ description: 'Awaiting payment, Confirmed or Canceled.'
104
+ },
105
+ {
106
+ id: 'name',
107
+ label: 'Name',
108
+ group: 'customer',
109
+ type: 'text',
110
+ aliases: [
111
+ 'customer',
112
+ 'customer name'
113
+ ]
114
+ },
115
+ {
116
+ id: 'email',
117
+ label: 'Email',
118
+ group: 'customer',
119
+ type: 'email',
120
+ aliases: [
121
+ 'customer email'
122
+ ]
123
+ },
124
+ {
125
+ id: 'phone',
126
+ label: 'Phone',
127
+ group: 'customer',
128
+ type: 'phone'
129
+ },
130
+ {
131
+ id: 'address',
132
+ label: 'Address',
133
+ group: 'customer',
134
+ type: 'longText',
135
+ description: 'Where the job is, as the customer typed it, when the service asked.'
136
+ },
137
+ {
138
+ id: 'crmRef',
139
+ label: 'CRM record',
140
+ group: 'customer',
141
+ type: 'text',
142
+ description: 'The CRM record the booking link carried, as kind:id.'
143
+ },
144
+ {
145
+ id: 'paidAmount',
146
+ label: 'Amount paid',
147
+ group: 'payment',
148
+ type: 'currency',
149
+ description: 'In US dollars, tax included.'
150
+ },
151
+ {
152
+ id: 'tax',
153
+ label: 'Tax',
154
+ group: 'payment',
155
+ type: 'currency',
156
+ description: 'In US dollars.'
157
+ },
158
+ {
159
+ id: 'fee',
160
+ label: 'Platform fee',
161
+ group: 'payment',
162
+ type: 'currency',
163
+ description: 'The platform’s fee on the charge, in US dollars.'
164
+ },
165
+ {
166
+ id: 'refunded',
167
+ label: 'Refunded',
168
+ group: 'payment',
169
+ type: 'currency',
170
+ description: 'In US dollars.'
171
+ },
172
+ {
173
+ id: 'taxMode',
174
+ label: 'Tax mode',
175
+ group: 'payment',
176
+ type: 'text',
177
+ description: 'How tax was charged on the booking.'
178
+ }
179
+ ].map(readOnly);
180
+ /** Computed from the booking and its service when read. */ const DERIVED = [
181
+ {
182
+ id: 'serviceCurrentName',
183
+ label: 'Service (current name)',
184
+ group: 'booking',
185
+ type: 'text',
186
+ description: 'The service’s name today; blank when the service no longer exists.'
187
+ },
188
+ {
189
+ id: 'startsLocal',
190
+ label: 'Starts (local time)',
191
+ group: 'booking',
192
+ type: 'text',
193
+ description: 'The start as a clock in the booking’s time zone read it.'
194
+ },
195
+ {
196
+ id: 'endsLocal',
197
+ label: 'Ends (local time)',
198
+ group: 'booking',
199
+ type: 'text',
200
+ description: 'The end as a clock in the booking’s time zone read it.'
201
+ },
202
+ {
203
+ id: 'durationMinutes',
204
+ label: 'Duration (minutes)',
205
+ group: 'booking',
206
+ type: 'integer'
207
+ }
208
+ ].map(readOnly);
209
+ /** Written by the platform: Stripe's handles and the booking's history. */ const SYSTEM = [
210
+ {
211
+ id: 'paymentIntentId',
212
+ label: 'Stripe payment ID',
213
+ group: 'payment',
214
+ type: 'text'
215
+ },
216
+ {
217
+ id: 'checkoutSessionId',
218
+ label: 'Stripe checkout session ID',
219
+ group: 'payment',
220
+ type: 'text'
221
+ },
222
+ {
223
+ id: 'holdExpiresAt',
224
+ label: 'Payment hold expires',
225
+ group: 'payment',
226
+ type: 'datetime',
227
+ description: 'When an unpaid booking’s slot is released.'
228
+ },
229
+ {
230
+ id: 'createdAt',
231
+ label: 'Booked',
232
+ group: 'system',
233
+ type: 'datetime'
234
+ },
235
+ {
236
+ id: 'confirmedAt',
237
+ label: 'Payment confirmed',
238
+ group: 'system',
239
+ type: 'datetime'
240
+ },
241
+ {
242
+ id: 'reminderSentAt',
243
+ label: 'Reminder sent',
244
+ group: 'system',
245
+ type: 'datetime'
246
+ },
247
+ {
248
+ id: 'customerErasedAt',
249
+ label: 'Customer erased',
250
+ group: 'system',
251
+ type: 'datetime',
252
+ description: 'When the customer’s details were erased from the booking.'
253
+ }
254
+ ].map(readOnly);
255
+ /** The catalog, the same for every site. */ export function bookingsTransferCatalog() {
256
+ return {
257
+ standard: STANDARD,
258
+ derived: DERIVED,
259
+ system: SYSTEM,
260
+ groups: BOOKINGS_TRANSFER_GROUPS
261
+ };
262
+ }
263
+ const STATUS_LABELS = {
264
+ pendingPayment: 'Awaiting payment',
265
+ confirmed: 'Confirmed',
266
+ canceled: 'Canceled'
267
+ };
268
+ /*==========================================
269
+ * READING A BOOKING
270
+ *=========================================*/ /** Epoch milliseconds from a stored moment: a number, a Timestamp, a Date or a string. */ function millisOf(value) {
271
+ if (value === null || value === undefined || value === '') return null;
272
+ if (typeof value === 'number') return Number.isFinite(value) ? value : null;
273
+ if (value instanceof Date) return Number.isFinite(value.getTime()) ? value.getTime() : null;
274
+ if (typeof value === 'string') {
275
+ const parsed = Date.parse(value);
276
+ return Number.isFinite(parsed) ? parsed : null;
277
+ }
278
+ if (typeof value === 'object') {
279
+ var _stamp_seconds, _ref, _stamp_nanoseconds;
280
+ const stamp = value;
281
+ if (typeof stamp.toMillis === 'function') return stamp.toMillis();
282
+ const seconds = (_stamp_seconds = stamp.seconds) != null ? _stamp_seconds : stamp._seconds;
283
+ if (typeof seconds === 'number') return seconds * 1000 + Math.floor(((_ref = (_stamp_nanoseconds = stamp.nanoseconds) != null ? _stamp_nanoseconds : stamp._nanoseconds) != null ? _ref : 0) / 1e6);
284
+ }
285
+ return null;
286
+ }
287
+ function isoOf(value) {
288
+ const ms = millisOf(value);
289
+ return ms === null ? null : new Date(ms).toISOString();
290
+ }
291
+ /** Dollars from stored cents; blank when the booking holds none. */ function dollarsOf(cents) {
292
+ if (cents === null || cents === undefined || cents === '') return null;
293
+ const value = Number(cents);
294
+ return Number.isFinite(value) ? Math.round(value) / 100 : null;
295
+ }
296
+ function textOf(value) {
297
+ if (value === null || value === undefined) return null;
298
+ const text = String(value);
299
+ return text === '' ? null : text;
300
+ }
301
+ const pad = (value)=>String(value).padStart(2, '0');
302
+ /** `2026-04-24 15:00` — the wall clock in `timeZone` at `atMs`. */ function localTextOf(atMs, timeZone) {
303
+ if (atMs === null) return null;
304
+ try {
305
+ const at = zonedDateTime(atMs, timeZone);
306
+ return `${at.year}-${pad(at.month)}-${pad(at.day)} ${pad(at.hour)}:${pad(at.minute)}`;
307
+ } catch (unused) {
308
+ return null;
309
+ }
310
+ }
311
+ function pageContext(db, ctx, hostId) {
312
+ let services = null;
313
+ let site = null;
314
+ const servicesOfSite = ()=>services != null ? services : services = db.collection('hosts').doc(hostId).collection('services').get().then((snapshot)=>new Map(snapshot.docs.map((doc)=>[
315
+ doc.id,
316
+ doc.data()
317
+ ])));
318
+ const siteZones = ()=>site != null ? site : site = Promise.all([
319
+ db.collection('hosts').doc(hostId).get(),
320
+ db.collection('orgs').doc(ctx.orgId).get()
321
+ ]).then(([host, org])=>{
322
+ var _host_data, _org_data;
323
+ return {
324
+ host: (_host_data = host.data()) != null ? _host_data : null,
325
+ org: (_org_data = org.data()) != null ? _org_data : null
326
+ };
327
+ });
328
+ return {
329
+ serviceOf: async (serviceId)=>{
330
+ var _get;
331
+ return (_get = (await servicesOfSite()).get(serviceId)) != null ? _get : null;
332
+ },
333
+ async zoneOf (booking) {
334
+ const stored = storedBookingTimeZone(booking);
335
+ if (stored) return stored;
336
+ // Written before the zone was stored: the booking route's own chain.
337
+ const [service, zones] = await Promise.all([
338
+ servicesOfSite().then((all)=>{
339
+ var _all_get, _booking_serviceId;
340
+ return (_all_get = all.get(String((_booking_serviceId = booking['serviceId']) != null ? _booking_serviceId : ''))) != null ? _all_get : null;
341
+ }),
342
+ siteZones()
343
+ ]);
344
+ return bookingTimeZone({
345
+ service: service,
346
+ host: zones.host,
347
+ org: zones.org
348
+ });
349
+ }
350
+ };
351
+ }
352
+ const ZONE_FIELDS = new Set([
353
+ 'timeZone',
354
+ 'startsLocal',
355
+ 'endsLocal'
356
+ ]);
357
+ /** One booking as a row holding only `fieldIds`. */ async function bookingRow(id, booking, fieldIds, page) {
358
+ const startsAtMs = millisOf(booking['startsAtMs']);
359
+ const endsAtMs = millisOf(booking['endsAtMs']);
360
+ const zone = fieldIds.some((fieldId)=>ZONE_FIELDS.has(fieldId)) ? await page.zoneOf(booking) : 'UTC';
361
+ const row = {};
362
+ for (const fieldId of fieldIds){
363
+ switch(fieldId){
364
+ case 'id':
365
+ row[fieldId] = id;
366
+ break;
367
+ case 'serviceId':
368
+ case 'serviceName':
369
+ case 'name':
370
+ case 'phone':
371
+ case 'address':
372
+ case 'crmRef':
373
+ case 'taxMode':
374
+ case 'paymentIntentId':
375
+ case 'checkoutSessionId':
376
+ row[fieldId] = textOf(booking[fieldId]);
377
+ break;
378
+ case 'email':
379
+ // `null` once the customer was erased.
380
+ row[fieldId] = textOf(booking['email']);
381
+ break;
382
+ case 'serviceCurrentName':
383
+ {
384
+ var _booking_serviceId;
385
+ const service = await page.serviceOf(String((_booking_serviceId = booking['serviceId']) != null ? _booking_serviceId : ''));
386
+ row[fieldId] = service && !service['deletedAt'] ? textOf(service['name']) : null;
387
+ break;
388
+ }
389
+ case 'startsAt':
390
+ row[fieldId] = isoOf(startsAtMs);
391
+ break;
392
+ case 'endsAt':
393
+ row[fieldId] = isoOf(endsAtMs);
394
+ break;
395
+ case 'timeZone':
396
+ row[fieldId] = zone;
397
+ break;
398
+ case 'startsLocal':
399
+ row[fieldId] = localTextOf(startsAtMs, zone);
400
+ break;
401
+ case 'endsLocal':
402
+ row[fieldId] = localTextOf(endsAtMs, zone);
403
+ break;
404
+ case 'durationMinutes':
405
+ row[fieldId] = startsAtMs !== null && endsAtMs !== null ? Math.round((endsAtMs - startsAtMs) / 60000) : null;
406
+ break;
407
+ case 'status':
408
+ {
409
+ var _STATUS_LABELS_status;
410
+ const status = textOf(booking['status']);
411
+ row[fieldId] = status ? (_STATUS_LABELS_status = STATUS_LABELS[status]) != null ? _STATUS_LABELS_status : status : null;
412
+ break;
413
+ }
414
+ case 'paidAmount':
415
+ row[fieldId] = dollarsOf(booking['paidAmountCents']);
416
+ break;
417
+ case 'tax':
418
+ row[fieldId] = dollarsOf(booking['taxCents']);
419
+ break;
420
+ case 'fee':
421
+ row[fieldId] = dollarsOf(booking['feeCents']);
422
+ break;
423
+ case 'refunded':
424
+ row[fieldId] = dollarsOf(booking['refundedCents']);
425
+ break;
426
+ case 'holdExpiresAt':
427
+ // Only an unpaid booking holds its slot; the stamp is cleared on payment.
428
+ row[fieldId] = isoOf(booking['expiresAtMs']);
429
+ break;
430
+ case 'createdAt':
431
+ case 'confirmedAt':
432
+ case 'reminderSentAt':
433
+ row[fieldId] = isoOf(booking[fieldId]);
434
+ break;
435
+ case 'customerErasedAt':
436
+ row[fieldId] = isoOf(booking['customerErasedAtMs']);
437
+ break;
438
+ default:
439
+ row[fieldId] = null;
440
+ }
441
+ }
442
+ return row;
443
+ }
444
+ function filteredQuery(bookings, filter, nowMs) {
445
+ if (!filter) return {
446
+ query: bookings,
447
+ orderField: 'startsAtMs'
448
+ };
449
+ switch(filter.kind){
450
+ case 'upcoming':
451
+ var _filter_asOfMs;
452
+ return {
453
+ query: bookings.where('endsAtMs', '>=', (_filter_asOfMs = filter.asOfMs) != null ? _filter_asOfMs : nowMs),
454
+ orderField: 'endsAtMs'
455
+ };
456
+ case 'email':
457
+ return {
458
+ query: bookings.where('email', '==', filter.email),
459
+ orderField: 'startsAtMs'
460
+ };
461
+ case 'service':
462
+ return {
463
+ query: bookings.where('serviceId', '==', filter.serviceId),
464
+ orderField: 'startsAtMs'
465
+ };
466
+ }
467
+ }
468
+ function readQueryCursor(cursor) {
469
+ if (cursor === null) return null;
470
+ try {
471
+ const parsed = JSON.parse(cursor);
472
+ if (typeof parsed.at === 'number' && typeof parsed.id === 'string' && parsed.id) {
473
+ return {
474
+ at: parsed.at,
475
+ id: parsed.id
476
+ };
477
+ }
478
+ } catch (unused) {
479
+ // Falls through to the refusal.
480
+ }
481
+ throw new Error('bookings export: the page cursor could not be read');
482
+ }
483
+ function hostIdOf(ctx) {
484
+ var _ctx_hostId;
485
+ const hostId = String((_ctx_hostId = ctx.hostId) != null ? _ctx_hostId : '').trim();
486
+ if (!hostId || hostId.includes('/')) throw new Error('bookings export: a site is required');
487
+ return hostId;
488
+ }
489
+ function pageSizeOf(options) {
490
+ var _ref;
491
+ const size = Math.floor(Number((_ref = options == null ? void 0 : options.pageSize) != null ? _ref : DEFAULT_PAGE_ROWS));
492
+ return Math.max(1, Math.min(MAX_PAGE_ROWS, Number.isFinite(size) ? size : DEFAULT_PAGE_ROWS));
493
+ }
494
+ function chunks(items, size) {
495
+ const out = [];
496
+ for(let index = 0; index < items.length; index += size)out.push(items.slice(index, index + size));
497
+ return out;
498
+ }
499
+ /*==========================================
500
+ * THE RESOURCE
501
+ *=========================================*/ /** The `bookings` resource over `deps.firestore()`. Export only: a lookup by id, and no apply or revert. */ export function createBookingsTransferResource(deps) {
502
+ var _deps_now;
503
+ const now = (_deps_now = deps.now) != null ? _deps_now : Date.now;
504
+ const bookingsOf = (hostId)=>deps.firestore().collection('hosts').doc(hostId).collection('bookings');
505
+ return {
506
+ fields: ()=>bookingsTransferCatalog(),
507
+ matchKeys: BOOKINGS_TRANSFER_MATCH_KEYS,
508
+ async count (ctx, options) {
509
+ const hostId = hostIdOf(ctx);
510
+ const bookings = bookingsOf(hostId);
511
+ if (options.ids) {
512
+ // Only the ids that name a booking, so the count is the rows the file holds.
513
+ const ids = [
514
+ ...new Set(options.ids)
515
+ ].filter((id)=>id && !id.includes('/'));
516
+ let total = 0;
517
+ for (const part of chunks(ids, IN_LIMIT)){
518
+ const snapshot = await bookings.where(FieldPath.documentId(), 'in', part).count().get();
519
+ total += snapshot.data().count;
520
+ }
521
+ return total;
522
+ }
523
+ const { query } = filteredQuery(bookings, parseBookingsTransferFilter(options.filter), now());
524
+ return (await query.count().get()).data().count;
525
+ },
526
+ async readPage (ctx, cursor, fieldIds, options) {
527
+ var _last_get;
528
+ const hostId = hostIdOf(ctx);
529
+ const db = deps.firestore();
530
+ const bookings = bookingsOf(hostId);
531
+ const size = pageSizeOf(options);
532
+ const page = pageContext(db, ctx, hostId);
533
+ if (options == null ? void 0 : options.ids) {
534
+ const ids = [
535
+ ...new Set(options.ids)
536
+ ].filter((id)=>id && !id.includes('/'));
537
+ const offset = cursor === null ? 0 : Number(cursor);
538
+ if (!Number.isInteger(offset) || offset < 0) {
539
+ throw new Error('bookings export: the page cursor could not be read');
540
+ }
541
+ const slice = ids.slice(offset, offset + size);
542
+ const snapshots = slice.length ? await db.getAll(...slice.map((id)=>bookings.doc(id))) : [];
543
+ const rows = [];
544
+ for (const snapshot of snapshots){
545
+ var _snapshot_data;
546
+ if (snapshot.exists) rows.push(await bookingRow(snapshot.id, (_snapshot_data = snapshot.data()) != null ? _snapshot_data : {}, fieldIds, page));
547
+ }
548
+ const next = offset + slice.length;
549
+ return {
550
+ rows,
551
+ next: next < ids.length ? String(next) : null
552
+ };
553
+ }
554
+ const { query, orderField } = filteredQuery(bookings, parseBookingsTransferFilter(options == null ? void 0 : options.filter), now());
555
+ let ordered = query.orderBy(orderField, 'asc').orderBy(FieldPath.documentId(), 'asc');
556
+ const after = readQueryCursor(cursor);
557
+ if (after) ordered = ordered.startAfter(after.at, after.id);
558
+ // One more than the page, to know without a second read whether another follows.
559
+ const snapshot = await ordered.limit(size + 1).get();
560
+ const docs = snapshot.docs.slice(0, size);
561
+ const rows = [];
562
+ for (const doc of docs)rows.push(await bookingRow(doc.id, doc.data(), fieldIds, page));
563
+ const last = docs[docs.length - 1];
564
+ const next = snapshot.docs.length > size && last ? JSON.stringify({
565
+ at: Number((_last_get = last.get(orderField)) != null ? _last_get : 0),
566
+ id: last.id
567
+ }) : null;
568
+ return {
569
+ rows,
570
+ next
571
+ };
572
+ },
573
+ /** What a booking holds now, by its Aglyn ID — the one key a resource nothing imports into answers. */ async lookup (ctx, requests) {
574
+ var _catalog_standard, _catalog_system;
575
+ const hostId = hostIdOf(ctx);
576
+ const db = deps.firestore();
577
+ const bookings = bookingsOf(hostId);
578
+ const byId = requests.filter((request)=>request.fieldId === TRANSFER_ID_FIELD);
579
+ const ids = [
580
+ ...new Set(byId.flatMap((request)=>request.values))
581
+ ].filter((id)=>id && !id.includes('/'));
582
+ const catalog = bookingsTransferCatalog();
583
+ const fieldIds = [
584
+ TRANSFER_ID_FIELD,
585
+ ...[
586
+ ...(_catalog_standard = catalog.standard) != null ? _catalog_standard : [],
587
+ ...(_catalog_system = catalog.system) != null ? _catalog_system : []
588
+ ].map((field)=>field.id)
589
+ ];
590
+ const page = pageContext(db, ctx, hostId);
591
+ const records = new Map();
592
+ for (const part of chunks(ids, MAX_PAGE_ROWS)){
593
+ const snapshots = part.length ? await db.getAll(...part.map((id)=>bookings.doc(id))) : [];
594
+ for (const snapshot of snapshots){
595
+ var _snapshot_data;
596
+ if (snapshot.exists) records.set(snapshot.id, await bookingRow(snapshot.id, (_snapshot_data = snapshot.data()) != null ? _snapshot_data : {}, fieldIds, page));
597
+ }
598
+ }
599
+ const lookup = buildMatchLookup([
600
+ ...records
601
+ ].map(([id, values])=>({
602
+ id,
603
+ values
604
+ })), byId);
605
+ return {
606
+ lookup,
607
+ records
608
+ };
609
+ }
610
+ };
611
+ }
612
+
613
+ //# sourceMappingURL=bookings-transfer.js.map