mattrguerra/meridian-ops-boardmainPrivate on GitHub, published here in full

meetings.ts

Language TypeScript2.2 KB67 lines

packages/fathom-client/src/resources/meetings.ts

  1. /**
  2. * Meetings, and the action items inside them.
  3. *
  4. * The unit of work is the action item, but the unit of access is the meeting:
  5. * who was invited decides who may see anything about it, so the two travel
  6. * together and this resource never returns items detached from their meeting.
  7. */
  8. import { FATHOM_PAGE_SIZE } from '../constants.js'
  9. import type { FathomClient } from '../http.js'
  10. import type { FathomMeeting, MeetingWindow } from '../types.js'
  11. interface ListResponse {
  12. items?: FathomMeeting[]
  13. /**
  14. * The end sentinel is an empty string, not null and not absent (verified
  15. * live). A loop written as `while (cursor !== undefined)` never terminates.
  16. * Same family as the Plinth unassigned sentinel: trust the wire over the
  17. * docs.
  18. */
  19. next_cursor?: string | null
  20. limit?: number
  21. }
  22. export class MeetingsResource {
  23. constructor(private readonly client: FathomClient) {}
  24. /**
  25. * Meetings created since `sinceIso`, newest first, with their action items.
  26. *
  27. * `include_action_items` is deliberately the only include set. It is not in
  28. * Fathom's "heavy request" list, so this stays on the 60-calls/60s global
  29. * bucket; adding include_summary or include_transcript would move every call
  30. * into the bucket that can be squeezed to five a minute.
  31. *
  32. * A page cap that is hit returns `truncated: true` with everything fetched so
  33. * far. Never throw away fetched data to guard against a hypothetical.
  34. */
  35. async listSince(sinceIso: string, maxPages = 20): Promise<MeetingWindow> {
  36. const meetings: FathomMeeting[] = []
  37. let cursor: string | undefined
  38. let pages = 0
  39. let truncated = true
  40. for (let page = 0; page < maxPages; page++) {
  41. const body = await this.client.get<ListResponse>('/meetings', {
  42. created_after: sinceIso,
  43. include_action_items: true,
  44. limit: FATHOM_PAGE_SIZE,
  45. cursor,
  46. })
  47. pages = page + 1
  48. meetings.push(...(body.items ?? []))
  49. // Empty string, null and undefined all mean "no more pages". Only a
  50. // non-empty cursor continues the sweep.
  51. const next = body.next_cursor
  52. if (!next) {
  53. truncated = false
  54. break
  55. }
  56. cursor = next
  57. }
  58. return { meetings, truncated, pages }
  59. }
  60. }