@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/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog — `@ai-matrx/messaging`
2
2
 
3
+ ## 0.14.0
4
+
5
+ ### Changed
6
+
7
+ - **Messaging runs with no organization selected** (access belongs to the
8
+ person, 2026-09-23). `<MessagingProvider organizationId={null}>` used to stay
9
+ inert — every inbox and conversation spun forever — and
10
+ `createMessagingRepository` threw at construction without an org, although
11
+ no read here filters by organization. Now the provider starts and every read
12
+ works as the signed-in person. The organization is only a write destination:
13
+ - starting a NEW conversation (`getOrCreateDirectConversation`,
14
+ `createGroupConversation`, `useConversations().startDirect`) without one is
15
+ refused with the new error code **`organization-required`**, before
16
+ anything reaches the database;
17
+ - a message or member added to an EXISTING conversation with no active
18
+ organization is stamped with that conversation's own organization;
19
+ - AI actions do not render without an organization (a run is filed under one).
20
+ - `MessagingIdentity.organizationId` and `MessagingRepository.organizationId`
21
+ are now `OrganizationId | null`; `RepositoryOptions.organizationId` is
22
+ optional. `MessagingErrorCode` gains `"organization-required"`.
23
+
24
+ ### Consumer action
25
+
26
+ - Pass the active organization as before, including `null` when none is
27
+ selected — do not gate `<MessagingProvider>` on having one.
28
+ - Where the host starts a conversation, ask the person to choose an
29
+ organization when none is selected (or handle `error.code ===
30
+ "organization-required"`).
31
+ - Code that read `host.identity.organizationId` as non-null must handle `null`.
32
+
3
33
  ## 0.13.1
4
34
 
5
35
  ### Fixed
package/README.md CHANGED
@@ -82,7 +82,11 @@ notifications race it into two "direct" conversations for one pair, forever.
82
82
  RLS plus auth-checked SECURITY DEFINER RPCs. There is not one permission branch in this package —
83
83
  an RPC that says no becomes a `forbidden` error, never a client-side re-decision. And every
84
84
  conversation and message write carries an explicit `organization_id`; the package refuses the
85
- write rather than letting an unscoped row reach the database.
85
+ write rather than letting an unscoped row reach the database. The organization is a write
86
+ DESTINATION only: `organizationId={null}` is a valid state in which every read works (the inbox,
87
+ a deep-linked conversation, replying — a reply is stamped with its conversation's organization),
88
+ and starting a NEW conversation is refused as `organization-required` so the host can ask the
89
+ person to choose one.
86
90
 
87
91
  ### 7. A missing session is a lifecycle moment, not an error.
88
92
 
package/dist/index.cjs CHANGED
@@ -931,7 +931,7 @@ function projectMessage(row, fallbackOrganizationId) {
931
931
  id: requiredStr(row, "id", operation),
932
932
  conversationId: requiredStr(row, "conversation_id", operation),
933
933
  senderId: requiredStr(row, "sender_id", operation),
934
- organizationId: str(row, "organization_id") ?? fallbackOrganizationId,
934
+ organizationId: str(row, "organization_id") ?? fallbackOrganizationId ?? "",
935
935
  content: typeof row["content"] === "string" ? row["content"] : "",
936
936
  kind: MESSAGE_KINDS.has(kindRaw) ? kindRaw : "text",
937
937
  // A row read from the DB is at least `sent`. `sending`/`failed` are outbox
@@ -979,7 +979,7 @@ function projectConversationSummary(row, viewerId, fallbackOrganizationId) {
979
979
  groupName,
980
980
  groupImageUrl,
981
981
  createdBy: createdBy === null ? null : createdBy,
982
- organizationId: str(row, "organization_id") ?? fallbackOrganizationId,
982
+ organizationId: str(row, "organization_id") ?? fallbackOrganizationId ?? "",
983
983
  createdAt,
984
984
  updatedAt,
985
985
  metadata: jsonObject(row["metadata"])
@@ -1312,7 +1312,11 @@ function createMessagingEngine(options) {
1312
1312
  optimisticMessage({
1313
1313
  conversationId: entry.draft.conversationId,
1314
1314
  senderId: identity.userId,
1315
- organizationId: identity.organizationId,
1315
+ // Client-only until the confirmed row replaces it: the active
1316
+ // organization, else the conversation's own.
1317
+ organizationId: identity.organizationId ?? store.snapshot().conversations.find(
1318
+ (item) => item.conversation.id === entry.draft.conversationId
1319
+ )?.conversation.organizationId ?? "",
1316
1320
  content: entry.draft.content,
1317
1321
  clientMessageId: entry.clientMessageId,
1318
1322
  ...entry.draft.kind !== void 0 ? { kind: entry.draft.kind } : {},
@@ -1844,12 +1848,15 @@ var RPCS = {
1844
1848
  };
1845
1849
  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";
1846
1850
  var CONVERSATION_COLUMNS = "id,type,group_name,group_image_url,created_by,organization_id,created_at,updated_at,metadata";
1851
+ function optionalOrg(organizationId) {
1852
+ return typeof organizationId === "string" && organizationId.length > 0 ? organizationId : null;
1853
+ }
1847
1854
  function requireOrg(organizationId, operation) {
1848
- if (typeof organizationId !== "string" || organizationId.length === 0) {
1855
+ if (organizationId === null) {
1849
1856
  throw new MessagingError(
1850
- "misconfigured",
1851
- `${operation}: no organization_id`,
1852
- "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."
1857
+ "organization-required",
1858
+ `${operation}: no organization selected`,
1859
+ "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."
1853
1860
  );
1854
1861
  }
1855
1862
  return organizationId;
@@ -1861,7 +1868,7 @@ function sleep(ms) {
1861
1868
  return new Promise((resolve) => setTimeout(resolve, jittered));
1862
1869
  }
1863
1870
  function createMessagingRepository(options) {
1864
- const org = requireOrg(options.organizationId, "createMessagingRepository");
1871
+ const org = optionalOrg(options.organizationId);
1865
1872
  const handedIn = options.identity;
1866
1873
  if (handedIn !== void 0 && handedIn !== null && handedIn.userId !== void 0) {
1867
1874
  options.onDiagnostic?.(
@@ -1931,6 +1938,11 @@ function createMessagingRepository(options) {
1931
1938
  });
1932
1939
  return found;
1933
1940
  }
1941
+ async function organizationOfExisting(conversationId) {
1942
+ if (org !== null) return org;
1943
+ const conversation = await repository.getConversation(conversationId);
1944
+ return conversation.organizationId;
1945
+ }
1934
1946
  const repository = {
1935
1947
  organizationId: org,
1936
1948
  actingUserId,
@@ -2063,6 +2075,7 @@ function createMessagingRepository(options) {
2063
2075
  },
2064
2076
  async getOrCreateDirectConversation(otherUserId) {
2065
2077
  const operation = "getOrCreateDirectConversation";
2078
+ const destination = requireOrg(org, operation);
2066
2079
  const me = await actingUser(operation);
2067
2080
  const id = await withSessionRetry(
2068
2081
  operation,
@@ -2074,7 +2087,7 @@ function createMessagingRepository(options) {
2074
2087
  // not a preference.
2075
2088
  p_user1_id: me,
2076
2089
  p_user2_id: otherUserId,
2077
- p_organization_id: org
2090
+ p_organization_id: destination
2078
2091
  },
2079
2092
  operation
2080
2093
  )
@@ -2086,13 +2099,14 @@ function createMessagingRepository(options) {
2086
2099
  },
2087
2100
  async createGroupConversation({ name, memberIds }) {
2088
2101
  const operation = "createGroupConversation";
2102
+ const destination = requireOrg(org, operation);
2089
2103
  const me = await actingUser(operation);
2090
2104
  const { data, error } = await withSessionRetry(
2091
2105
  operation,
2092
2106
  async () => db().from(TABLES.conversations).insert({
2093
2107
  type: "group",
2094
2108
  group_name: name,
2095
- organization_id: org,
2109
+ organization_id: destination,
2096
2110
  created_by: me
2097
2111
  }).select("id").single()
2098
2112
  );
@@ -2107,7 +2121,7 @@ function createMessagingRepository(options) {
2107
2121
  conversation_id: conversationId,
2108
2122
  user_id: userId,
2109
2123
  role: userId === me ? "owner" : "member",
2110
- organization_id: org,
2124
+ organization_id: destination,
2111
2125
  created_by: me
2112
2126
  }))
2113
2127
  );
@@ -2117,10 +2131,11 @@ function createMessagingRepository(options) {
2117
2131
  async insertMessage(draft, clientMessageId) {
2118
2132
  const operation = "insertMessage";
2119
2133
  const me = await actingUser(operation);
2134
+ const destination = await organizationOfExisting(draft.conversationId);
2120
2135
  const { data, error } = await db().from(TABLES.messages).insert({
2121
2136
  conversation_id: draft.conversationId,
2122
2137
  sender_id: me,
2123
- organization_id: org,
2138
+ organization_id: destination,
2124
2139
  created_by: me,
2125
2140
  content: draft.content,
2126
2141
  message_type: draft.kind ?? "text",
@@ -2181,12 +2196,13 @@ function createMessagingRepository(options) {
2181
2196
  async addMembers(conversationId, memberIds) {
2182
2197
  const operation = "addMembers";
2183
2198
  const me = await actingUser(operation);
2199
+ const destination = await organizationOfExisting(conversationId);
2184
2200
  const { error } = await db().from(TABLES.participants).insert(
2185
2201
  memberIds.map((userId) => ({
2186
2202
  conversation_id: conversationId,
2187
2203
  user_id: userId,
2188
2204
  role: "member",
2189
- organization_id: org,
2205
+ organization_id: destination,
2190
2206
  created_by: me
2191
2207
  }))
2192
2208
  );