@ai-matrx/messaging 0.13.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.cjs CHANGED
@@ -869,7 +869,7 @@ function projectMessage(row, fallbackOrganizationId) {
869
869
  id: requiredStr(row, "id", operation),
870
870
  conversationId: requiredStr(row, "conversation_id", operation),
871
871
  senderId: requiredStr(row, "sender_id", operation),
872
- organizationId: str(row, "organization_id") ?? fallbackOrganizationId,
872
+ organizationId: str(row, "organization_id") ?? fallbackOrganizationId ?? "",
873
873
  content: typeof row["content"] === "string" ? row["content"] : "",
874
874
  kind: MESSAGE_KINDS.has(kindRaw) ? kindRaw : "text",
875
875
  // A row read from the DB is at least `sent`. `sending`/`failed` are outbox
@@ -917,7 +917,7 @@ function projectConversationSummary(row, viewerId, fallbackOrganizationId) {
917
917
  groupName,
918
918
  groupImageUrl,
919
919
  createdBy: createdBy === null ? null : createdBy,
920
- organizationId: str(row, "organization_id") ?? fallbackOrganizationId,
920
+ organizationId: str(row, "organization_id") ?? fallbackOrganizationId ?? "",
921
921
  createdAt,
922
922
  updatedAt,
923
923
  metadata: jsonObject(row["metadata"])
@@ -1250,7 +1250,11 @@ function createMessagingEngine(options) {
1250
1250
  optimisticMessage({
1251
1251
  conversationId: entry.draft.conversationId,
1252
1252
  senderId: identity.userId,
1253
- organizationId: identity.organizationId,
1253
+ // Client-only until the confirmed row replaces it: the active
1254
+ // organization, else the conversation's own.
1255
+ organizationId: identity.organizationId ?? store.snapshot().conversations.find(
1256
+ (item) => item.conversation.id === entry.draft.conversationId
1257
+ )?.conversation.organizationId ?? "",
1254
1258
  content: entry.draft.content,
1255
1259
  clientMessageId: entry.clientMessageId,
1256
1260
  ...entry.draft.kind !== void 0 ? { kind: entry.draft.kind } : {},
@@ -1739,12 +1743,15 @@ var RPCS = {
1739
1743
  };
1740
1744
  var MESSAGE_COLUMNS = "id,conversation_id,sender_id,organization_id,content,message_type,status,reply_to_id,client_message_id,action_data,media_url,media_thumbnail_url,media_metadata,created_at,edited_at,deleted_at,deleted_for_everyone,metadata";
1741
1745
  var CONVERSATION_COLUMNS = "id,type,group_name,group_image_url,created_by,organization_id,created_at,updated_at,metadata";
1746
+ function optionalOrg(organizationId) {
1747
+ return typeof organizationId === "string" && organizationId.length > 0 ? organizationId : null;
1748
+ }
1742
1749
  function requireOrg(organizationId, operation) {
1743
- if (typeof organizationId !== "string" || organizationId.length === 0) {
1750
+ if (organizationId === null) {
1744
1751
  throw new MessagingError(
1745
- "misconfigured",
1746
- `${operation}: no organization_id`,
1747
- "Every conversation and message write carries an explicit organization_id (R5). Pass a real org on <MessagingProvider>; the package refuses the write rather than letting an unscoped row reach the database."
1752
+ "organization-required",
1753
+ `${operation}: no organization selected`,
1754
+ "A new conversation is filed under one organization. Ask the person to choose one, then pass it as organizationId on <MessagingProvider>. Reading and replying to existing conversations never needs it."
1748
1755
  );
1749
1756
  }
1750
1757
  return organizationId;
@@ -1756,7 +1763,7 @@ function sleep(ms) {
1756
1763
  return new Promise((resolve) => setTimeout(resolve, jittered));
1757
1764
  }
1758
1765
  function createMessagingRepository(options) {
1759
- const org = requireOrg(options.organizationId, "createMessagingRepository");
1766
+ const org = optionalOrg(options.organizationId);
1760
1767
  const handedIn = options.identity;
1761
1768
  if (handedIn !== void 0 && handedIn !== null && handedIn.userId !== void 0) {
1762
1769
  options.onDiagnostic?.(
@@ -1826,6 +1833,11 @@ function createMessagingRepository(options) {
1826
1833
  });
1827
1834
  return found;
1828
1835
  }
1836
+ async function organizationOfExisting(conversationId) {
1837
+ if (org !== null) return org;
1838
+ const conversation = await repository.getConversation(conversationId);
1839
+ return conversation.organizationId;
1840
+ }
1829
1841
  const repository = {
1830
1842
  organizationId: org,
1831
1843
  actingUserId,
@@ -1958,6 +1970,7 @@ function createMessagingRepository(options) {
1958
1970
  },
1959
1971
  async getOrCreateDirectConversation(otherUserId) {
1960
1972
  const operation = "getOrCreateDirectConversation";
1973
+ const destination = requireOrg(org, operation);
1961
1974
  const me = await actingUser(operation);
1962
1975
  const id = await withSessionRetry(
1963
1976
  operation,
@@ -1969,7 +1982,7 @@ function createMessagingRepository(options) {
1969
1982
  // not a preference.
1970
1983
  p_user1_id: me,
1971
1984
  p_user2_id: otherUserId,
1972
- p_organization_id: org
1985
+ p_organization_id: destination
1973
1986
  },
1974
1987
  operation
1975
1988
  )
@@ -1981,13 +1994,14 @@ function createMessagingRepository(options) {
1981
1994
  },
1982
1995
  async createGroupConversation({ name, memberIds }) {
1983
1996
  const operation = "createGroupConversation";
1997
+ const destination = requireOrg(org, operation);
1984
1998
  const me = await actingUser(operation);
1985
1999
  const { data, error } = await withSessionRetry(
1986
2000
  operation,
1987
2001
  async () => db().from(TABLES.conversations).insert({
1988
2002
  type: "group",
1989
2003
  group_name: name,
1990
- organization_id: org,
2004
+ organization_id: destination,
1991
2005
  created_by: me
1992
2006
  }).select("id").single()
1993
2007
  );
@@ -2002,7 +2016,7 @@ function createMessagingRepository(options) {
2002
2016
  conversation_id: conversationId,
2003
2017
  user_id: userId,
2004
2018
  role: userId === me ? "owner" : "member",
2005
- organization_id: org,
2019
+ organization_id: destination,
2006
2020
  created_by: me
2007
2021
  }))
2008
2022
  );
@@ -2012,10 +2026,11 @@ function createMessagingRepository(options) {
2012
2026
  async insertMessage(draft, clientMessageId) {
2013
2027
  const operation = "insertMessage";
2014
2028
  const me = await actingUser(operation);
2029
+ const destination = await organizationOfExisting(draft.conversationId);
2015
2030
  const { data, error } = await db().from(TABLES.messages).insert({
2016
2031
  conversation_id: draft.conversationId,
2017
2032
  sender_id: me,
2018
- organization_id: org,
2033
+ organization_id: destination,
2019
2034
  created_by: me,
2020
2035
  content: draft.content,
2021
2036
  message_type: draft.kind ?? "text",
@@ -2076,12 +2091,13 @@ function createMessagingRepository(options) {
2076
2091
  async addMembers(conversationId, memberIds) {
2077
2092
  const operation = "addMembers";
2078
2093
  const me = await actingUser(operation);
2094
+ const destination = await organizationOfExisting(conversationId);
2079
2095
  const { error } = await db().from(TABLES.participants).insert(
2080
2096
  memberIds.map((userId) => ({
2081
2097
  conversation_id: conversationId,
2082
2098
  user_id: userId,
2083
2099
  role: "member",
2084
- organization_id: org,
2100
+ organization_id: destination,
2085
2101
  created_by: me
2086
2102
  }))
2087
2103
  );
@@ -2192,7 +2208,7 @@ function MessagingProvider(props) {
2192
2208
  }, [strayUserId]);
2193
2209
  const session = useSessionUserId(client, props.onDiagnostic);
2194
2210
  const userId = session.snapshot.userId;
2195
- const ready = client != null && userId !== null && typeof organizationId === "string" && organizationId.length > 0;
2211
+ const ready = client != null && userId !== null;
2196
2212
  const hostProvidesRealtime = (0, import_react2.useIsRealtimeProviderMounted)();
2197
2213
  const runtime = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2198
2214
  MessagingRuntime,
@@ -2228,12 +2244,13 @@ function MessagingRuntime(props) {
2228
2244
  actionRegistry.register(handler);
2229
2245
  });
2230
2246
  }, [actionRegistry, handlers]);
2231
- const ready = client != null && manager !== null && sessionIdentity !== null && userId !== null && typeof organizationId === "string" && organizationId.length > 0;
2247
+ const ready = client != null && manager !== null && sessionIdentity !== null && userId !== null;
2248
+ const activeOrganizationId = typeof organizationId === "string" && organizationId.length > 0 ? organizationId : null;
2232
2249
  const engine = (0, import_react.useMemo)(() => {
2233
2250
  if (!ready || manager === null || sessionIdentity === null || userId === null) return null;
2234
2251
  const identity = {
2235
2252
  userId,
2236
- organizationId
2253
+ organizationId: activeOrganizationId
2237
2254
  };
2238
2255
  const report = (event) => {
2239
2256
  (diagnosticRef.current ?? defaultDiagnostics)(event);
@@ -2244,7 +2261,7 @@ function MessagingRuntime(props) {
2244
2261
  // supabase-shape.ts); everything below this line works against the rich
2245
2262
  // contract, which `supabase-shape.test.ts` proves a real client honors.
2246
2263
  client,
2247
- organizationId,
2264
+ organizationId: activeOrganizationId,
2248
2265
  // THE SAME READER the render loop above uses. Two readers would be two
2249
2266
  // answers during the tick an account switch takes.
2250
2267
  sessionIdentity,
@@ -2277,14 +2294,14 @@ function MessagingRuntime(props) {
2277
2294
  }
2278
2295
  });
2279
2296
  return built;
2280
- }, [ready, manager, sessionIdentity, userId, organizationId]);
2297
+ }, [ready, manager, sessionIdentity, userId, activeOrganizationId]);
2281
2298
  (0, import_react.useEffect)(() => {
2282
2299
  if (engine === null) return void 0;
2283
2300
  void engine.start().catch((error) => {
2284
2301
  (diagnosticRef.current ?? defaultDiagnostics)({
2285
2302
  level: "error",
2286
2303
  message: `Messaging failed to start: ${String(error?.message ?? error)}`,
2287
- remedy: "Check the signed-in session and organization, then reload the surface."
2304
+ remedy: "Check the signed-in session, then reload the surface."
2288
2305
  });
2289
2306
  });
2290
2307
  return () => {
@@ -2298,9 +2315,10 @@ function MessagingRuntime(props) {
2298
2315
  const sourceFeature = props.sourceFeature;
2299
2316
  const ai = (0, import_react.useMemo)(() => {
2300
2317
  if (transport === void 0 || agents === void 0 || !ready) return null;
2318
+ if (activeOrganizationId === null) return null;
2301
2319
  return createMessagingAi({
2302
2320
  transport,
2303
- organizationId,
2321
+ organizationId: activeOrganizationId,
2304
2322
  agents,
2305
2323
  ...maxTranscriptMessages !== void 0 ? { maxTranscriptMessages } : {},
2306
2324
  ...sourceApp !== void 0 ? { sourceApp } : {},
@@ -2310,7 +2328,7 @@ function MessagingRuntime(props) {
2310
2328
  transport,
2311
2329
  agents,
2312
2330
  ready,
2313
- organizationId,
2331
+ activeOrganizationId,
2314
2332
  maxTranscriptMessages,
2315
2333
  sourceApp,
2316
2334
  sourceFeature
@@ -2361,7 +2379,7 @@ function useRequiredMessagingHost() {
2361
2379
  const host = useMessagingHost();
2362
2380
  if (host === null) {
2363
2381
  throw new Error(
2364
- "[@ai-matrx/messaging] No messaging host. Wrap this tree in <MessagingProvider client={supabase} userId={userId} organizationId={orgId}> and render it only once auth has resolved."
2382
+ "[@ai-matrx/messaging] No messaging host. Wrap this tree in <MessagingProvider client={supabase} organizationId={orgId}> and render it only once auth has resolved."
2365
2383
  );
2366
2384
  }
2367
2385
  return host;
@@ -2751,7 +2769,8 @@ function useMessageAction(message) {
2751
2769
  const context = {
2752
2770
  messageId: message.id,
2753
2771
  actorId: host.identity.userId,
2754
- organizationId: host.identity.organizationId,
2772
+ // The active organization, else the organization the message lives in.
2773
+ organizationId: host.identity.organizationId ?? message.organizationId,
2755
2774
  choice
2756
2775
  };
2757
2776
  host.actions.execute(action, context).then(setReceipt).catch((cause) => {