@ai-matrx/meet 0.7.1 → 0.7.3

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/dist/index.cjs CHANGED
@@ -69,6 +69,7 @@ __export(src_exports, {
69
69
  TRANSCRIPTION_ATTRIBUTES: () => TRANSCRIPTION_ATTRIBUTES,
70
70
  TRANSCRIPTION_TOPIC: () => TRANSCRIPTION_TOPIC,
71
71
  absenceSentence: () => absenceSentence,
72
+ announceMeeting: () => announceMeeting,
72
73
  applyRoomEvent: () => applyRoomEvent,
73
74
  asCallId: () => asCallId,
74
75
  asMeetingId: () => asMeetingId,
@@ -83,6 +84,7 @@ __export(src_exports, {
83
84
  canModerateParticipant: () => canModerateParticipant,
84
85
  canTransition: () => canTransition,
85
86
  captionSegment: () => captionSegment,
87
+ changesPatch: () => changesPatch,
86
88
  computeStage: () => computeStage,
87
89
  countLabel: () => countLabel,
88
90
  createCallCenter: () => createCallCenter,
@@ -127,6 +129,7 @@ __export(src_exports, {
127
129
  mapQuality: () => mapQuality,
128
130
  meetingInviteAction: () => meetingInviteAction,
129
131
  meetingLink: () => meetingLink,
132
+ meetingPath: () => meetingPath,
130
133
  meetingSlug: () => meetingSlug,
131
134
  meetingTopic: () => meetingTopic,
132
135
  meetingWhen: () => meetingWhen,
@@ -143,9 +146,15 @@ __export(src_exports, {
143
146
  parseRoomToken: () => parseRoomToken,
144
147
  participantSummary: () => participantSummary,
145
148
  playableRecordingFileId: () => playableRecordingFileId,
149
+ projectInvitee: () => projectInvitee,
146
150
  projectNote: () => projectNote,
151
+ projectOccurrence: () => projectOccurrence,
152
+ projectOverride: () => projectOverride,
147
153
  projectParticipant: () => projectParticipant,
154
+ projectRsvpInvitation: () => projectRsvpInvitation,
148
155
  projectSegment: () => projectSegment,
156
+ projectUpcoming: () => projectUpcoming,
157
+ pushMeetingToGoogleCalendar: () => pushMeetingToGoogleCalendar,
149
158
  readServerRefusal: () => readServerRefusal,
150
159
  readableTranscript: () => readableTranscript,
151
160
  recordingLabel: () => recordingLabel,
@@ -153,6 +162,7 @@ __export(src_exports, {
153
162
  roleOf: () => roleOf,
154
163
  roomTopic: () => roomTopic,
155
164
  setGuestRecordAccess: () => setGuestRecordAccess,
165
+ settingsPatch: () => settingsPatch,
156
166
  snapshotParticipant: () => snapshotParticipant,
157
167
  unavailableFrom: () => unavailableFrom,
158
168
  userTopic: () => userTopic,
@@ -187,7 +197,7 @@ var MeetError = class extends Error {
187
197
  };
188
198
  var FORBIDDEN_CODES = /* @__PURE__ */ new Set(["42501", "PGRST301", "PGRST302"]);
189
199
  var NOT_FOUND_CODES = /* @__PURE__ */ new Set(["PGRST116", "PGRST205"]);
190
- var CONFLICT_CODES = /* @__PURE__ */ new Set(["23505"]);
200
+ var CONFLICT_CODES = /* @__PURE__ */ new Set(["23505", "40001", "55000"]);
191
201
  var SESSION_MARKERS = [
192
202
  "auth session missing",
193
203
  "jwt expired",
@@ -345,8 +355,19 @@ var MEET_ROUTES = {
345
355
  */
346
356
  record: "/api/v1/meet/record",
347
357
  /** A host chooses what guests may read of ONE meeting (MRF-1). */
348
- recordAccess: "/api/v1/meet/record/access"
358
+ recordAccess: "/api/v1/meet/record/access",
359
+ /**
360
+ * Meet wave 1 (0.8): bring every invitee up to date after a write — sends the
361
+ * invitation / update / cancellation emails with their `.ics` and re-queues
362
+ * reminders. Idempotent. `{meeting_id}` is substituted by `meetingPath`.
363
+ */
364
+ meetingAnnounce: "/api/v1/meet/meetings/{meeting_id}/announce",
365
+ /** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */
366
+ meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar"
349
367
  };
368
+ function meetingPath(template, meetingId) {
369
+ return template.replace("{meeting_id}", encodeURIComponent(meetingId));
370
+ }
350
371
  var ROOM_TOKEN_HEADER = "x-meet-room-token";
351
372
  var REFRESH_RATIO = 0.8;
352
373
  function readString(source, key) {
@@ -476,6 +497,19 @@ function createTokenClient(options) {
476
497
  "The link is wrong or the meeting was deleted. Show the 'meeting not found' screen; a durable link that 404s is never a retry."
477
498
  );
478
499
  }
500
+ if (response.status === 410) {
501
+ let reason = "This meeting was cancelled.";
502
+ try {
503
+ const body = await response.json();
504
+ if (typeof body?.detail?.message === "string") reason = body.detail.message;
505
+ } catch {
506
+ }
507
+ throw new MeetError(
508
+ "cancelled",
509
+ reason,
510
+ "Nobody can join a cancelled meeting. Show the cancelled screen with this sentence; ask the host to schedule a new one."
511
+ );
512
+ }
479
513
  if (response.status === 423) {
480
514
  throw new MeetError(
481
515
  "room-locked",
@@ -677,12 +711,12 @@ function createMeetAi(options) {
677
711
  async function dispatch(route, body, operation) {
678
712
  const response = await send({ path: route, body, operation, requireAuth: true });
679
713
  if (!response.ok) {
680
- const refusal = await refusalFor(response, operation);
681
- options.store.patch({ noteTakerRefusal: refusal });
714
+ const refusal2 = await refusalFor(response, operation);
715
+ options.store.patch({ noteTakerRefusal: refusal2 });
682
716
  throw new MeetError(
683
717
  response.status === 403 ? "forbidden" : "transport",
684
- refusal.reason,
685
- refusal.remedy
718
+ refusal2.reason,
719
+ refusal2.remedy
686
720
  );
687
721
  }
688
722
  options.store.patch({ noteTakerRefusal: null });
@@ -720,11 +754,11 @@ function createMeetAi(options) {
720
754
  ...signal !== void 0 ? { signal } : {}
721
755
  });
722
756
  if (!response.ok) {
723
- const refusal = await refusalFor(response, "meet.ai.ask");
757
+ const refusal2 = await refusalFor(response, "meet.ai.ask");
724
758
  throw new MeetError(
725
759
  response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : "transport",
726
- refusal.reason,
727
- refusal.remedy
760
+ refusal2.reason,
761
+ refusal2.remedy
728
762
  );
729
763
  }
730
764
  const body = response.body;
@@ -833,14 +867,14 @@ function noteTakerStatus(args) {
833
867
  const row = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
834
868
  const recorded = readState(row["state"]);
835
869
  const detail = typeof row["reason"] === "string" && row["reason"].length > 0 ? row["reason"] : null;
836
- const refusal = args.refusal ?? null;
870
+ const refusal2 = args.refusal ?? null;
837
871
  if (recorded === null) {
838
- return { state: "absent", detail, present, refusal };
872
+ return { state: "absent", detail, present, refusal: refusal2 };
839
873
  }
840
874
  if (recorded === "transcribing" && !present) {
841
- return { state: "handoff_pending", detail, present, refusal };
875
+ return { state: "handoff_pending", detail, present, refusal: refusal2 };
842
876
  }
843
- return { state: recorded, detail, present, refusal };
877
+ return { state: recorded, detail, present, refusal: refusal2 };
844
878
  }
845
879
  function noteTakerLabel(status) {
846
880
  switch (status.state) {
@@ -1253,7 +1287,11 @@ var TABLES = {
1253
1287
  invites: "meet_call_invites",
1254
1288
  recordings: "meet_recordings",
1255
1289
  transcripts: "meet_transcript_segments",
1256
- notes: "meet_notes"
1290
+ notes: "meet_notes",
1291
+ /** Wave 1 (0.8): who a meeting was sent to, and what they answered. */
1292
+ invitees: "meet_invitees",
1293
+ /** Wave 1 (0.8): one occurrence of a series cancelled or moved. */
1294
+ occurrenceOverrides: "meet_occurrence_overrides"
1257
1295
  };
1258
1296
  var RPCS = {
1259
1297
  /** Atomic: create the invite row and stamp its expiry in one statement. */
@@ -1269,10 +1307,41 @@ var RPCS = {
1269
1307
  admit: "meet_admit_participant",
1270
1308
  setLock: "meet_set_lock",
1271
1309
  endMeeting: "meet_end_meeting",
1272
- recordConsent: "meet_record_consent"
1310
+ recordConsent: "meet_record_consent",
1311
+ /*
1312
+ * ── MEET WAVE 1 — management (0.8). Every write is a SECURITY DEFINER door
1313
+ * authorized by the meeting's host or a real iam grant (editor to change,
1314
+ * admin = co-host to archive or name a co-host) — never an organization
1315
+ * match. After any of these writes, call `announceMeeting` so the server
1316
+ * sends invitations / updates / cancellations and re-queues reminders.
1317
+ */
1318
+ /** Schedule with every setting at create; unset settings come from the knobs. */
1319
+ scheduleMeeting: "meet_schedule_meeting",
1320
+ /** Edit / reschedule — a JSON patch with a version compare-and-swap. */
1321
+ updateMeeting: "meet_update_meeting",
1322
+ /** Cancel is a state (`cancelled_at`); the link keeps resolving. */
1323
+ cancelMeeting: "meet_cancel_meeting",
1324
+ /** Archive (soft delete) — the invitations and exceptions cascade. */
1325
+ archiveMeeting: "meet_archive_meeting",
1326
+ restoreMeeting: "meet_restore_meeting",
1327
+ /** Cancel, move or restore ONE occurrence of a series by its original start. */
1328
+ setOccurrence: "meet_set_occurrence",
1329
+ addInvitees: "meet_add_invitees",
1330
+ setInviteeRole: "meet_set_invitee_role",
1331
+ removeInvitee: "meet_remove_invitee",
1332
+ /** A signed-in invitee answers for themselves. */
1333
+ respond: "meet_respond",
1334
+ /** The emailed RSVP link (no account): read and answer. */
1335
+ invitationByToken: "meet_invitation_by_token",
1336
+ respondByToken: "meet_respond_by_token",
1337
+ /** One meeting's occurrences between two instants (INVOKER — the reader's RLS). */
1338
+ meetingOccurrences: "meet_meeting_occurrences",
1339
+ /** Every occurrence the reader can see between two instants — the Upcoming list. */
1340
+ occurrencesBetween: "meet_occurrences_between"
1273
1341
  };
1274
1342
  var VOID_RPCS = [RPCS.admit, RPCS.recordConsent];
1275
- var MEETING_COLUMNS = "id,organization_id,room_name,slug,title,kind,host_user_id,scheduled_for,scheduled_duration_minutes,started_at,ended_at,locked,lobby_enabled,recording_policy,ai_enabled,metadata";
1343
+ var MEETING_COLUMNS = "id,organization_id,room_name,slug,title,kind,host_user_id,scheduled_for,scheduled_duration_minutes,started_at,ended_at,locked,lobby_enabled,recording_policy,ai_enabled,metadata,time_zone,agenda,recurrence_rule,join_before_host,cancelled_at,cancellation_reason,deleted_at,calendar_sequence,version";
1344
+ var INVITEE_COLUMNS = "id,meeting_id,invitee_user_id,email,display_name,role,rsvp_state,responded_at,rsvp_note,last_sent_sequence,last_sent_at";
1276
1345
  var RECORDING_COLUMNS = "id,meeting_id,state,started_at,ended_at,started_by,file_id,failure_reason";
1277
1346
  var SEGMENT_COLUMNS = "id,meeting_id,identity,speaker,text,started_at,is_final,metadata";
1278
1347
  var NOTE_COLUMNS = "id,meeting_id,text,kind,assignee_user_id,created_at,metadata";
@@ -1370,6 +1439,138 @@ function projectNote(row) {
1370
1439
  source: "record"
1371
1440
  };
1372
1441
  }
1442
+ var RSVP_STATES = ["needs_action", "accepted", "declined", "tentative"];
1443
+ var OCCURRENCE_STATES = ["scheduled", "moved", "cancelled"];
1444
+ function rsvpState(value) {
1445
+ return RSVP_STATES.includes(value) ? value : "needs_action";
1446
+ }
1447
+ function num(row, key) {
1448
+ const value = row[key];
1449
+ return typeof value === "number" ? value : null;
1450
+ }
1451
+ function when(row, key) {
1452
+ const value = row[key];
1453
+ if (value instanceof Date) return value.toISOString();
1454
+ return text(row, key);
1455
+ }
1456
+ function projectInvitee(row) {
1457
+ const operation = "meet.projectInvitee";
1458
+ const userId = text(row, "invitee_user_id");
1459
+ return {
1460
+ id: requireText(row, "id", operation),
1461
+ meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
1462
+ userId: userId === null ? null : asUserId(userId),
1463
+ email: text(row, "email"),
1464
+ displayName: text(row, "display_name"),
1465
+ role: text(row, "role") === "cohost" ? "cohost" : "invitee",
1466
+ rsvpState: rsvpState(text(row, "rsvp_state")),
1467
+ respondedAt: when(row, "responded_at"),
1468
+ rsvpNote: text(row, "rsvp_note"),
1469
+ lastSentSequence: num(row, "last_sent_sequence"),
1470
+ lastSentAt: when(row, "last_sent_at")
1471
+ };
1472
+ }
1473
+ function projectOccurrence(row) {
1474
+ const operation = "meet.projectOccurrence";
1475
+ const state = text(row, "state");
1476
+ return {
1477
+ meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
1478
+ originalStart: when(row, "original_start") ?? requireText(row, "original_start", operation),
1479
+ occurrenceStart: when(row, "occurrence_start") ?? requireText(row, "occurrence_start", operation),
1480
+ durationMinutes: num(row, "duration_minutes"),
1481
+ // An unknown state is shown as scheduled, never dropped: the row is real.
1482
+ state: OCCURRENCE_STATES.includes(state) ? state : "scheduled",
1483
+ sequence: num(row, "sequence") ?? 0
1484
+ };
1485
+ }
1486
+ function projectUpcoming(row) {
1487
+ const operation = "meet.projectUpcoming";
1488
+ const kind = text(row, "kind");
1489
+ const myRole = text(row, "my_role");
1490
+ const myRsvp = text(row, "my_rsvp");
1491
+ return {
1492
+ ...projectOccurrence(row),
1493
+ organizationId: asOrganizationId(requireText(row, "organization_id", operation)),
1494
+ title: text(row, "title") ?? "Meeting",
1495
+ slug: requireText(row, "slug", operation),
1496
+ kind: kind === "scheduled" || kind === "recurring" ? kind : "instant",
1497
+ hostUserId: asUserId(requireText(row, "host_user_id", operation)),
1498
+ timeZone: text(row, "time_zone") ?? "UTC",
1499
+ meetingCancelled: bool(row, "meeting_cancelled", false),
1500
+ myRsvp: myRsvp === null ? null : rsvpState(myRsvp),
1501
+ myRole: myRole === "host" || myRole === "cohost" || myRole === "invitee" ? myRole : null
1502
+ };
1503
+ }
1504
+ function projectOverride(row) {
1505
+ const operation = "meet.projectOverride";
1506
+ return {
1507
+ id: requireText(row, "id", operation),
1508
+ meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
1509
+ originalStart: when(row, "original_start") ?? requireText(row, "original_start", operation),
1510
+ state: text(row, "state") === "moved" ? "moved" : "cancelled",
1511
+ newStart: when(row, "new_start"),
1512
+ newDurationMinutes: num(row, "new_duration_minutes"),
1513
+ reason: text(row, "reason"),
1514
+ calendarSequence: num(row, "calendar_sequence") ?? 0,
1515
+ deletedAt: when(row, "deleted_at")
1516
+ };
1517
+ }
1518
+ function projectRsvpInvitation(body) {
1519
+ const operation = "meet.projectRsvpInvitation";
1520
+ if (typeof body !== "object" || body === null) {
1521
+ throw invalidResponse(operation, "the RSVP door returned no object");
1522
+ }
1523
+ const row = body;
1524
+ if (row["ok"] !== true) {
1525
+ return {
1526
+ ok: false,
1527
+ message: text(row, "message") ?? "This invitation link is no longer valid \u2014 ask the host to send it again."
1528
+ };
1529
+ }
1530
+ const meeting = row["meeting"] ?? {};
1531
+ const invitee = row["invitee"] ?? {};
1532
+ return {
1533
+ ok: true,
1534
+ meeting: {
1535
+ title: text(meeting, "title") ?? "Meeting",
1536
+ slug: requireText(meeting, "slug", operation),
1537
+ scheduledFor: when(meeting, "scheduled_for"),
1538
+ durationMinutes: num(meeting, "duration_minutes"),
1539
+ timeZone: text(meeting, "time_zone") ?? "UTC",
1540
+ agenda: text(meeting, "agenda"),
1541
+ recurrenceRule: text(meeting, "recurrence_rule"),
1542
+ cancelledAt: when(meeting, "cancelled_at"),
1543
+ cancellationReason: text(meeting, "cancellation_reason"),
1544
+ endedAt: when(meeting, "ended_at")
1545
+ },
1546
+ invitee: {
1547
+ displayName: text(invitee, "display_name"),
1548
+ email: text(invitee, "email"),
1549
+ rsvpState: rsvpState(text(invitee, "rsvp_state")),
1550
+ respondedAt: when(invitee, "responded_at")
1551
+ }
1552
+ };
1553
+ }
1554
+ function settingsPatch(settings) {
1555
+ const patch = {};
1556
+ if (settings.lobbyEnabled !== void 0) patch["lobby_enabled"] = settings.lobbyEnabled;
1557
+ if (settings.recordingPolicy !== void 0) patch["recording_policy"] = settings.recordingPolicy;
1558
+ if (settings.aiEnabled !== void 0) patch["ai_enabled"] = settings.aiEnabled;
1559
+ if (settings.joinBeforeHost !== void 0) patch["join_before_host"] = settings.joinBeforeHost;
1560
+ return patch;
1561
+ }
1562
+ function changesPatch(changes) {
1563
+ const patch = settingsPatch(changes);
1564
+ if (changes.title !== void 0) patch["title"] = changes.title;
1565
+ if (changes.agenda !== void 0) patch["agenda"] = changes.agenda;
1566
+ if (changes.timeZone !== void 0) patch["time_zone"] = changes.timeZone;
1567
+ if (changes.scheduledFor !== void 0) patch["scheduled_for"] = changes.scheduledFor;
1568
+ if (changes.scheduledDurationMinutes !== void 0) {
1569
+ patch["scheduled_duration_minutes"] = changes.scheduledDurationMinutes;
1570
+ }
1571
+ if (changes.recurrenceRule !== void 0) patch["recurrence_rule"] = changes.recurrenceRule;
1572
+ return patch;
1573
+ }
1373
1574
  function createMeetRepository(options) {
1374
1575
  const db = options.client.schema(MEET_SCHEMA);
1375
1576
  async function guardVoid(operation, run) {
@@ -1439,7 +1640,16 @@ function createMeetRepository(options) {
1439
1640
  lobbyEnabled: bool(row, "lobby_enabled", true),
1440
1641
  recordingPolicy: policy === "disabled" || policy === "always-on" ? policy : "host-controlled",
1441
1642
  aiEnabled: bool(row, "ai_enabled", true),
1442
- metadata: typeof metadata === "object" && metadata !== null && !Array.isArray(metadata) ? metadata : {}
1643
+ metadata: typeof metadata === "object" && metadata !== null && !Array.isArray(metadata) ? metadata : {},
1644
+ timeZone: text(row, "time_zone") ?? "UTC",
1645
+ agenda: text(row, "agenda"),
1646
+ recurrenceRule: text(row, "recurrence_rule"),
1647
+ joinBeforeHost: bool(row, "join_before_host", true),
1648
+ cancelledAt: text(row, "cancelled_at"),
1649
+ cancellationReason: text(row, "cancellation_reason"),
1650
+ deletedAt: text(row, "deleted_at"),
1651
+ calendarSequence: typeof row["calendar_sequence"] === "number" ? row["calendar_sequence"] : 0,
1652
+ version: typeof row["version"] === "number" ? row["version"] : 1
1443
1653
  };
1444
1654
  }
1445
1655
  function projectRecording(row) {
@@ -1526,6 +1736,170 @@ function createMeetRepository(options) {
1526
1736
  projectNote,
1527
1737
  projectRecording,
1528
1738
  projectAttendee,
1739
+ projectInvitee,
1740
+ async scheduleMeeting(input) {
1741
+ requireOrg(input.organizationId, "meet.scheduleMeeting");
1742
+ const row = await guard(
1743
+ "meet.scheduleMeeting",
1744
+ () => db.rpc(RPCS.scheduleMeeting, {
1745
+ p_organization_id: input.organizationId,
1746
+ p_host_user_id: input.hostUserId,
1747
+ p_title: input.title,
1748
+ p_scheduled_for: input.scheduledFor,
1749
+ p_time_zone: input.timeZone,
1750
+ p_duration_minutes: input.durationMinutes ?? null,
1751
+ p_agenda: input.agenda ?? null,
1752
+ p_recurrence_rule: input.recurrenceRule ?? null,
1753
+ p_settings: settingsPatch(input.settings ?? {})
1754
+ })
1755
+ );
1756
+ return projectMeeting(row);
1757
+ },
1758
+ async updateMeeting(meetingId, changes, options2 = {}) {
1759
+ const row = await guard(
1760
+ "meet.updateMeeting",
1761
+ () => db.rpc(RPCS.updateMeeting, {
1762
+ p_meeting_id: meetingId,
1763
+ p_changes: changesPatch(changes),
1764
+ p_expected_version: options2.expectedVersion ?? null,
1765
+ p_by_user_id: options2.byUserId ?? null
1766
+ })
1767
+ );
1768
+ return projectMeeting(row);
1769
+ },
1770
+ async cancelMeeting(meetingId, reason, byUserId = null) {
1771
+ const row = await guard(
1772
+ "meet.cancelMeeting",
1773
+ () => db.rpc(RPCS.cancelMeeting, {
1774
+ p_meeting_id: meetingId,
1775
+ p_reason: reason,
1776
+ p_by_user_id: byUserId
1777
+ })
1778
+ );
1779
+ return projectMeeting(row);
1780
+ },
1781
+ async archiveMeeting(meetingId, byUserId = null) {
1782
+ const row = await guard(
1783
+ "meet.archiveMeeting",
1784
+ () => db.rpc(RPCS.archiveMeeting, { p_meeting_id: meetingId, p_by_user_id: byUserId })
1785
+ );
1786
+ return projectMeeting(row);
1787
+ },
1788
+ async restoreMeeting(meetingId, byUserId = null) {
1789
+ const row = await guard(
1790
+ "meet.restoreMeeting",
1791
+ () => db.rpc(RPCS.restoreMeeting, { p_meeting_id: meetingId, p_by_user_id: byUserId })
1792
+ );
1793
+ return projectMeeting(row);
1794
+ },
1795
+ async setOccurrence(args) {
1796
+ const row = await guard(
1797
+ "meet.setOccurrence",
1798
+ () => db.rpc(RPCS.setOccurrence, {
1799
+ p_meeting_id: args.meetingId,
1800
+ p_original_start: args.originalStart,
1801
+ p_action: args.action,
1802
+ p_new_start: args.newStart ?? null,
1803
+ p_new_duration_minutes: args.newDurationMinutes ?? null,
1804
+ p_reason: args.reason ?? null,
1805
+ p_by_user_id: args.byUserId ?? null
1806
+ })
1807
+ );
1808
+ return projectOverride(row);
1809
+ },
1810
+ async meetingOccurrences(meetingId, { from, to, limit = 200 }) {
1811
+ const rows = await guard(
1812
+ "meet.meetingOccurrences",
1813
+ () => db.rpc(RPCS.meetingOccurrences, {
1814
+ p_meeting_id: meetingId,
1815
+ p_from: from,
1816
+ p_to: to,
1817
+ p_limit: limit
1818
+ })
1819
+ );
1820
+ return rows.map(projectOccurrence);
1821
+ },
1822
+ async occurrencesBetween({ from, to, organizationId = null, limit = 500 }) {
1823
+ const rows = await guard(
1824
+ "meet.occurrencesBetween",
1825
+ () => db.rpc(RPCS.occurrencesBetween, {
1826
+ p_from: from,
1827
+ p_to: to,
1828
+ p_organization_id: organizationId,
1829
+ p_limit: limit
1830
+ })
1831
+ );
1832
+ return rows.map(projectUpcoming);
1833
+ },
1834
+ async invitees(meetingId) {
1835
+ const response = await db.from(TABLES.invitees).select(INVITEE_COLUMNS).eq("meeting_id", meetingId).is("deleted_at", null).order("created_at", { ascending: true }).order("id", { ascending: true });
1836
+ if (response.error !== null) throw normalizeMeetError(response.error, "meet.invitees");
1837
+ return (response.data ?? []).map(projectInvitee);
1838
+ },
1839
+ async addInvitees(meetingId, people, byUserId = null) {
1840
+ const rows = await guard(
1841
+ "meet.addInvitees",
1842
+ () => db.rpc(RPCS.addInvitees, {
1843
+ p_meeting_id: meetingId,
1844
+ p_invitees: people.map((person) => ({
1845
+ ...person.userId !== void 0 ? { user_id: person.userId } : {},
1846
+ ...person.email !== void 0 ? { email: person.email } : {},
1847
+ ...person.displayName !== void 0 ? { display_name: person.displayName } : {},
1848
+ ...person.role !== void 0 ? { role: person.role } : {}
1849
+ })),
1850
+ p_by_user_id: byUserId
1851
+ })
1852
+ );
1853
+ return rows.map(projectInvitee);
1854
+ },
1855
+ async setInviteeRole(inviteeId, role, byUserId = null) {
1856
+ const row = await guard(
1857
+ "meet.setInviteeRole",
1858
+ () => db.rpc(RPCS.setInviteeRole, {
1859
+ p_invitee_id: inviteeId,
1860
+ p_role: role,
1861
+ p_by_user_id: byUserId
1862
+ })
1863
+ );
1864
+ return projectInvitee(row);
1865
+ },
1866
+ async removeInvitee(inviteeId, byUserId = null) {
1867
+ const row = await guard(
1868
+ "meet.removeInvitee",
1869
+ () => db.rpc(RPCS.removeInvitee, { p_invitee_id: inviteeId, p_by_user_id: byUserId })
1870
+ );
1871
+ return projectInvitee(row);
1872
+ },
1873
+ async respond(meetingId, answer, note = null) {
1874
+ const row = await guard(
1875
+ "meet.respond",
1876
+ () => db.rpc(RPCS.respond, {
1877
+ p_meeting_id: meetingId,
1878
+ p_response: answer,
1879
+ p_note: note,
1880
+ p_by_user_id: null
1881
+ })
1882
+ );
1883
+ return projectInvitee(row);
1884
+ },
1885
+ async invitationByToken(secret) {
1886
+ const body = await guard(
1887
+ "meet.invitationByToken",
1888
+ () => db.rpc(RPCS.invitationByToken, { p_secret: secret })
1889
+ );
1890
+ return projectRsvpInvitation(body);
1891
+ },
1892
+ async respondByToken(secret, answer, note = null) {
1893
+ const body = await guard(
1894
+ "meet.respondByToken",
1895
+ () => db.rpc(RPCS.respondByToken, {
1896
+ p_secret: secret,
1897
+ p_response: answer,
1898
+ p_note: note
1899
+ })
1900
+ );
1901
+ return projectRsvpInvitation(body);
1902
+ },
1529
1903
  async attendees(meetingId) {
1530
1904
  const response = await db.from(TABLES.participants).select(ATTENDEE_COLUMNS).eq("meeting_id", meetingId).order("joined_at", { ascending: true }).order("id", { ascending: true });
1531
1905
  if (response.error !== null) throw normalizeMeetError(response.error, "meet.attendees");
@@ -2035,8 +2409,8 @@ function callStateLabel(state, outgoing) {
2035
2409
  return "Call";
2036
2410
  }
2037
2411
  }
2038
- function countLabel(count, singular, plural = `${singular}s`) {
2039
- return `${count} ${count === 1 ? singular : plural}`;
2412
+ function countLabel(count2, singular, plural = `${singular}s`) {
2413
+ return `${count2} ${count2 === 1 ? singular : plural}`;
2040
2414
  }
2041
2415
 
2042
2416
  // src/core/invitations.ts
@@ -2180,11 +2554,11 @@ function computeStage(args) {
2180
2554
  presenting: sharer !== null
2181
2555
  };
2182
2556
  }
2183
- function gridColumns(count) {
2184
- if (count <= 1) return 1;
2185
- if (count <= 4) return 2;
2186
- if (count <= 9) return 3;
2187
- if (count <= 16) return 4;
2557
+ function gridColumns(count2) {
2558
+ if (count2 <= 1) return 1;
2559
+ if (count2 <= 4) return 2;
2560
+ if (count2 <= 9) return 3;
2561
+ if (count2 <= 16) return 4;
2188
2562
  return 5;
2189
2563
  }
2190
2564
 
@@ -2861,6 +3235,15 @@ function createMeetingSession(options) {
2861
3235
  const { manager, repository, store, identity } = options;
2862
3236
  let channel = null;
2863
3237
  let meetingChannel = null;
3238
+ async function readMeeting(meetingId) {
3239
+ try {
3240
+ return await repository.meeting(meetingId);
3241
+ } catch (error) {
3242
+ const slug = store.snapshot().meeting?.slug;
3243
+ if (slug === void 0) throw error;
3244
+ return repository.meetingBySlug(slug);
3245
+ }
3246
+ }
2864
3247
  function requireHostUser(operation) {
2865
3248
  if (identity.userId === null || !isHostRole(effectiveRole(store.snapshot()))) {
2866
3249
  throw new MeetError(
@@ -2941,7 +3324,7 @@ function createMeetingSession(options) {
2941
3324
  onBackfill: async () => {
2942
3325
  const meeting = store.snapshot().meeting;
2943
3326
  if (meeting === null) return;
2944
- const fresh = isGuest(identity) ? await repository.meetingBySlug(meeting.slug) : await repository.meeting(meeting.id);
3327
+ const fresh = isGuest(identity) ? await repository.meetingBySlug(meeting.slug) : await readMeeting(meeting.id);
2945
3328
  store.patch({ meeting: fresh, locked: fresh.locked });
2946
3329
  }
2947
3330
  });
@@ -3036,7 +3419,7 @@ function createMeetingSession(options) {
3036
3419
  // a recovered surface from looking healthy and being wrong.
3037
3420
  onBackfill: async () => {
3038
3421
  const [meeting, transcript, notes, recordings, attendees] = await Promise.all([
3039
- repository.meeting(meetingId),
3422
+ readMeeting(meetingId),
3040
3423
  repository.transcript(meetingId),
3041
3424
  repository.notes(meetingId),
3042
3425
  repository.recordings(meetingId),
@@ -3148,11 +3531,11 @@ function createMeetingSession(options) {
3148
3531
  } catch {
3149
3532
  body = null;
3150
3533
  }
3151
- const refusal = readServerRefusal(body);
3534
+ const refusal2 = readServerRefusal(body);
3152
3535
  throw new MeetError(
3153
3536
  response.status === 403 ? "forbidden" : "transport",
3154
- refusal?.reason ?? `meet.endForEveryone: the server answered ${response.status}`,
3155
- refusal?.remedy !== void 0 && refusal.remedy.length > 0 ? refusal.remedy : "The meeting is still running. Only a host may end it, and the server re-checks that \u2014 being a host on this screen is not the decision."
3537
+ refusal2?.reason ?? `meet.endForEveryone: the server answered ${response.status}`,
3538
+ refusal2?.remedy !== void 0 && refusal2.remedy.length > 0 ? refusal2.remedy : "The meeting is still running. Only a host may end it, and the server re-checks that \u2014 being a host on this screen is not the decision."
3156
3539
  );
3157
3540
  }
3158
3541
  let payload = null;
@@ -3280,6 +3663,57 @@ function fileDownloadPath(fileId) {
3280
3663
  return `/files/${encodeURIComponent(fileId)}/download?inline=1`;
3281
3664
  }
3282
3665
 
3666
+ // src/core/management.ts
3667
+ async function refusal(response, operation) {
3668
+ let body = null;
3669
+ try {
3670
+ body = await response.json();
3671
+ } catch {
3672
+ body = null;
3673
+ }
3674
+ const said = readServerRefusal(body);
3675
+ return new MeetError(
3676
+ response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
3677
+ `${operation}: ${said?.reason ?? `the server answered ${response.status}`}`,
3678
+ said?.remedy || "The meeting itself is saved \u2014 only the telling failed. Try again; the call is idempotent."
3679
+ );
3680
+ }
3681
+ function count(body, key) {
3682
+ const value = body[key];
3683
+ return typeof value === "number" ? value : 0;
3684
+ }
3685
+ async function announceMeeting(api, meetingId) {
3686
+ const operation = "meet.announceMeeting";
3687
+ const response = await api.request({
3688
+ path: meetingPath(MEET_ROUTES.meetingAnnounce, meetingId),
3689
+ method: "POST",
3690
+ requireAuth: true,
3691
+ operation
3692
+ });
3693
+ if (!response.ok) throw await refusal(response, operation);
3694
+ const body = await response.json();
3695
+ return {
3696
+ invitations: count(body, "invitations"),
3697
+ updates: count(body, "updates"),
3698
+ cancellations: count(body, "cancellations"),
3699
+ alreadyCurrent: count(body, "already_current"),
3700
+ remindersQueued: count(body, "reminders_queued"),
3701
+ remindersCancelled: count(body, "reminders_cancelled")
3702
+ };
3703
+ }
3704
+ async function pushMeetingToGoogleCalendar(api, meetingId, target) {
3705
+ const operation = "meet.pushMeetingToGoogleCalendar";
3706
+ const response = await api.request({
3707
+ path: meetingPath(MEET_ROUTES.meetingGoogleCalendar, meetingId),
3708
+ method: "POST",
3709
+ requireAuth: true,
3710
+ operation,
3711
+ body: { connection_id: target.connectionId, calendar_id: target.calendarId ?? "primary" }
3712
+ });
3713
+ if (!response.ok) throw await refusal(response, operation);
3714
+ return await response.json();
3715
+ }
3716
+
3283
3717
  // src/core/room.ts
3284
3718
  var PARTICIPANT_ROLE_ATTRIBUTE = "matrx.participant.role";
3285
3719
  var PARTICIPANT_CAN_RECORD_ATTRIBUTE = "matrx.participant.can_record";
@@ -3854,7 +4288,7 @@ function meetingWhen(meeting, locale) {
3854
4288
  if (start === null) return "Time not recorded";
3855
4289
  const startedAt = new Date(start);
3856
4290
  if (Number.isNaN(startedAt.getTime())) return "Time not recorded";
3857
- const when = startedAt.toLocaleString(locale, {
4291
+ const when2 = startedAt.toLocaleString(locale, {
3858
4292
  weekday: "long",
3859
4293
  year: "numeric",
3860
4294
  month: "long",
@@ -3862,11 +4296,11 @@ function meetingWhen(meeting, locale) {
3862
4296
  hour: "numeric",
3863
4297
  minute: "2-digit"
3864
4298
  });
3865
- if (meeting.endedAt === null || meeting.startedAt === null) return when;
4299
+ if (meeting.endedAt === null || meeting.startedAt === null) return when2;
3866
4300
  const endedAt = new Date(meeting.endedAt);
3867
- if (Number.isNaN(endedAt.getTime())) return when;
4301
+ if (Number.isNaN(endedAt.getTime())) return when2;
3868
4302
  const elapsedMs = endedAt.getTime() - startedAt.getTime();
3869
- return `${when} \xB7 ${(0, import_format.formatDurationMs)(elapsedMs, { style: "long" })}`;
4303
+ return `${when2} \xB7 ${(0, import_format.formatDurationMs)(elapsedMs, { style: "long" })}`;
3870
4304
  }
3871
4305
  var GUEST_RECORD_ACCESS_LEVELS = [
3872
4306
  "none",
@@ -3942,8 +4376,8 @@ async function loadGuestMeetingRecord(args) {
3942
4376
  );
3943
4377
  }
3944
4378
  if (!response.ok) {
3945
- const refusal = await readRefusal(response);
3946
- return refusedBundle(meeting, refusal.message, refusal.remedy);
4379
+ const refusal2 = await readRefusal(response);
4380
+ return refusedBundle(meeting, refusal2.message, refusal2.remedy);
3947
4381
  }
3948
4382
  const body = await response.json();
3949
4383
  const payload = typeof body["data"] === "object" && body["data"] !== null ? body["data"] : body;
@@ -4034,8 +4468,8 @@ async function setGuestRecordAccess(args) {
4034
4468
  }
4035
4469
  });
4036
4470
  if (!response.ok) {
4037
- const refusal = await readRefusal(response);
4038
- throw new MeetError("forbidden", refusal.message, refusal.remedy);
4471
+ const refusal2 = await readRefusal(response);
4472
+ throw new MeetError("forbidden", refusal2.message, refusal2.remedy);
4039
4473
  }
4040
4474
  const body = await response.json();
4041
4475
  const payload = typeof body["data"] === "object" && body["data"] !== null ? body["data"] : body;