@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 +30 -0
- package/README.md +5 -1
- package/dist/index.cjs +29 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -9
- package/dist/index.d.ts +28 -9
- package/dist/index.js +29 -13
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +42 -23
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +34 -10
- package/dist/react.d.ts +34 -10
- package/dist/react.js +42 -23
- package/dist/react.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -207,7 +207,11 @@ interface Page<TItem, TCursor> {
|
|
|
207
207
|
/** What the host injects. Identity ONLY — every hard part is in the package. */
|
|
208
208
|
interface MessagingIdentity {
|
|
209
209
|
readonly userId: UserId;
|
|
210
|
-
|
|
210
|
+
/**
|
|
211
|
+
* The active organization, or `null` when none is selected. Reads never
|
|
212
|
+
* depend on it; it is only where a NEW conversation lands.
|
|
213
|
+
*/
|
|
214
|
+
readonly organizationId: OrganizationId | null;
|
|
211
215
|
}
|
|
212
216
|
interface DraftMessage {
|
|
213
217
|
readonly conversationId: ConversationId;
|
|
@@ -628,7 +632,9 @@ declare function createOutbox(options: OutboxOptions): Outbox;
|
|
|
628
632
|
* errors in 0.6 seconds. A missing session is a NORMAL lifecycle moment — it
|
|
629
633
|
* warns and retries; it is never a red error.
|
|
630
634
|
*/
|
|
631
|
-
type MessagingErrorCode = "session-unavailable" | "contention" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "misconfigured"
|
|
635
|
+
type MessagingErrorCode = "session-unavailable" | "contention" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "misconfigured"
|
|
636
|
+
/** A NEW conversation needs an organization to land in and none is selected. */
|
|
637
|
+
| "organization-required" | "unknown";
|
|
632
638
|
declare class MessagingError extends Error {
|
|
633
639
|
readonly code: MessagingErrorCode;
|
|
634
640
|
/** The remedy. Nothing fails silently, and nothing fails without saying what to do. */
|
|
@@ -920,15 +926,21 @@ interface RepositoryOptions {
|
|
|
920
926
|
*/
|
|
921
927
|
client: MessagingSupabaseClient;
|
|
922
928
|
/**
|
|
923
|
-
* The active organization
|
|
924
|
-
*
|
|
929
|
+
* The active organization, or `null`/omitted when none is selected.
|
|
930
|
+
*
|
|
931
|
+
* 🚨 READS NEVER NEED IT (access belongs to the person, 2026-09-23). Every
|
|
932
|
+
* read here is answered by RLS as the session's user; the organization is
|
|
933
|
+
* only the DESTINATION of a NEW conversation. Without one, starting a
|
|
934
|
+
* conversation is refused with `organization-required` (the host asks the
|
|
935
|
+
* person to choose), and a message or member added to an EXISTING
|
|
936
|
+
* conversation is stamped with that conversation's own organization.
|
|
925
937
|
*
|
|
926
938
|
* 🚨 THERE IS NO `userId` HERE, AND THERE WILL NOT BE ONE (DD-241). Who is
|
|
927
939
|
* acting is read from `client`'s own session — see `session-identity.ts` for
|
|
928
940
|
* the class that closed and why a handed-in id could not be made safe by
|
|
929
941
|
* convention. A caller that still passes `identity` is IGNORED and told so.
|
|
930
942
|
*/
|
|
931
|
-
organizationId
|
|
943
|
+
organizationId?: string | null | undefined;
|
|
932
944
|
/**
|
|
933
945
|
* INTERNAL. The React provider already runs one session reader for its own
|
|
934
946
|
* render loop and hands it here so the two cannot answer differently and so
|
|
@@ -949,8 +961,11 @@ interface RepositoryOptions {
|
|
|
949
961
|
now?: (() => number) | undefined;
|
|
950
962
|
}
|
|
951
963
|
interface MessagingRepository {
|
|
952
|
-
/**
|
|
953
|
-
|
|
964
|
+
/**
|
|
965
|
+
* The active organization new conversations land in, or `null` when none is
|
|
966
|
+
* selected — reads work either way.
|
|
967
|
+
*/
|
|
968
|
+
readonly organizationId: OrganizationId | null;
|
|
954
969
|
/**
|
|
955
970
|
* Who the session says is acting — awaited, because the first `getSession()`
|
|
956
971
|
* is a real read. Rejects with `session-unavailable` when nobody is signed
|
|
@@ -1311,9 +1326,13 @@ declare function formatLastSeen(lastSeenMs: number | null, now?: number): string
|
|
|
1311
1326
|
|
|
1312
1327
|
declare function projectUserSummary(row: Record<string, unknown>): UserSummary;
|
|
1313
1328
|
declare function projectMessageAction(value: unknown): MessageAction | null;
|
|
1314
|
-
declare function projectMessage(row: Record<string, unknown>,
|
|
1329
|
+
declare function projectMessage(row: Record<string, unknown>,
|
|
1330
|
+
/** The active organization, or `null` when none is selected (reads never need one). */
|
|
1331
|
+
fallbackOrganizationId: OrganizationId | null): Message;
|
|
1315
1332
|
declare function projectParticipantRole(value: unknown): ParticipantRole;
|
|
1316
|
-
declare function projectConversationSummary(row: Record<string, unknown>, viewerId: UserId,
|
|
1333
|
+
declare function projectConversationSummary(row: Record<string, unknown>, viewerId: UserId,
|
|
1334
|
+
/** The active organization, or `null` when none is selected (reads never need one). */
|
|
1335
|
+
fallbackOrganizationId: OrganizationId | null): ConversationSummary;
|
|
1317
1336
|
|
|
1318
1337
|
/**
|
|
1319
1338
|
* MATRX REFERENCES — a message can name a platform entity, and everything it
|
package/dist/index.d.ts
CHANGED
|
@@ -207,7 +207,11 @@ interface Page<TItem, TCursor> {
|
|
|
207
207
|
/** What the host injects. Identity ONLY — every hard part is in the package. */
|
|
208
208
|
interface MessagingIdentity {
|
|
209
209
|
readonly userId: UserId;
|
|
210
|
-
|
|
210
|
+
/**
|
|
211
|
+
* The active organization, or `null` when none is selected. Reads never
|
|
212
|
+
* depend on it; it is only where a NEW conversation lands.
|
|
213
|
+
*/
|
|
214
|
+
readonly organizationId: OrganizationId | null;
|
|
211
215
|
}
|
|
212
216
|
interface DraftMessage {
|
|
213
217
|
readonly conversationId: ConversationId;
|
|
@@ -628,7 +632,9 @@ declare function createOutbox(options: OutboxOptions): Outbox;
|
|
|
628
632
|
* errors in 0.6 seconds. A missing session is a NORMAL lifecycle moment — it
|
|
629
633
|
* warns and retries; it is never a red error.
|
|
630
634
|
*/
|
|
631
|
-
type MessagingErrorCode = "session-unavailable" | "contention" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "misconfigured"
|
|
635
|
+
type MessagingErrorCode = "session-unavailable" | "contention" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "misconfigured"
|
|
636
|
+
/** A NEW conversation needs an organization to land in and none is selected. */
|
|
637
|
+
| "organization-required" | "unknown";
|
|
632
638
|
declare class MessagingError extends Error {
|
|
633
639
|
readonly code: MessagingErrorCode;
|
|
634
640
|
/** The remedy. Nothing fails silently, and nothing fails without saying what to do. */
|
|
@@ -920,15 +926,21 @@ interface RepositoryOptions {
|
|
|
920
926
|
*/
|
|
921
927
|
client: MessagingSupabaseClient;
|
|
922
928
|
/**
|
|
923
|
-
* The active organization
|
|
924
|
-
*
|
|
929
|
+
* The active organization, or `null`/omitted when none is selected.
|
|
930
|
+
*
|
|
931
|
+
* 🚨 READS NEVER NEED IT (access belongs to the person, 2026-09-23). Every
|
|
932
|
+
* read here is answered by RLS as the session's user; the organization is
|
|
933
|
+
* only the DESTINATION of a NEW conversation. Without one, starting a
|
|
934
|
+
* conversation is refused with `organization-required` (the host asks the
|
|
935
|
+
* person to choose), and a message or member added to an EXISTING
|
|
936
|
+
* conversation is stamped with that conversation's own organization.
|
|
925
937
|
*
|
|
926
938
|
* 🚨 THERE IS NO `userId` HERE, AND THERE WILL NOT BE ONE (DD-241). Who is
|
|
927
939
|
* acting is read from `client`'s own session — see `session-identity.ts` for
|
|
928
940
|
* the class that closed and why a handed-in id could not be made safe by
|
|
929
941
|
* convention. A caller that still passes `identity` is IGNORED and told so.
|
|
930
942
|
*/
|
|
931
|
-
organizationId
|
|
943
|
+
organizationId?: string | null | undefined;
|
|
932
944
|
/**
|
|
933
945
|
* INTERNAL. The React provider already runs one session reader for its own
|
|
934
946
|
* render loop and hands it here so the two cannot answer differently and so
|
|
@@ -949,8 +961,11 @@ interface RepositoryOptions {
|
|
|
949
961
|
now?: (() => number) | undefined;
|
|
950
962
|
}
|
|
951
963
|
interface MessagingRepository {
|
|
952
|
-
/**
|
|
953
|
-
|
|
964
|
+
/**
|
|
965
|
+
* The active organization new conversations land in, or `null` when none is
|
|
966
|
+
* selected — reads work either way.
|
|
967
|
+
*/
|
|
968
|
+
readonly organizationId: OrganizationId | null;
|
|
954
969
|
/**
|
|
955
970
|
* Who the session says is acting — awaited, because the first `getSession()`
|
|
956
971
|
* is a real read. Rejects with `session-unavailable` when nobody is signed
|
|
@@ -1311,9 +1326,13 @@ declare function formatLastSeen(lastSeenMs: number | null, now?: number): string
|
|
|
1311
1326
|
|
|
1312
1327
|
declare function projectUserSummary(row: Record<string, unknown>): UserSummary;
|
|
1313
1328
|
declare function projectMessageAction(value: unknown): MessageAction | null;
|
|
1314
|
-
declare function projectMessage(row: Record<string, unknown>,
|
|
1329
|
+
declare function projectMessage(row: Record<string, unknown>,
|
|
1330
|
+
/** The active organization, or `null` when none is selected (reads never need one). */
|
|
1331
|
+
fallbackOrganizationId: OrganizationId | null): Message;
|
|
1315
1332
|
declare function projectParticipantRole(value: unknown): ParticipantRole;
|
|
1316
|
-
declare function projectConversationSummary(row: Record<string, unknown>, viewerId: UserId,
|
|
1333
|
+
declare function projectConversationSummary(row: Record<string, unknown>, viewerId: UserId,
|
|
1334
|
+
/** The active organization, or `null` when none is selected (reads never need one). */
|
|
1335
|
+
fallbackOrganizationId: OrganizationId | null): ConversationSummary;
|
|
1317
1336
|
|
|
1318
1337
|
/**
|
|
1319
1338
|
* MATRX REFERENCES — a message can name a platform entity, and everything it
|
package/dist/index.js
CHANGED
|
@@ -861,7 +861,7 @@ function projectMessage(row, fallbackOrganizationId) {
|
|
|
861
861
|
id: requiredStr(row, "id", operation),
|
|
862
862
|
conversationId: requiredStr(row, "conversation_id", operation),
|
|
863
863
|
senderId: requiredStr(row, "sender_id", operation),
|
|
864
|
-
organizationId: str(row, "organization_id") ?? fallbackOrganizationId,
|
|
864
|
+
organizationId: str(row, "organization_id") ?? fallbackOrganizationId ?? "",
|
|
865
865
|
content: typeof row["content"] === "string" ? row["content"] : "",
|
|
866
866
|
kind: MESSAGE_KINDS.has(kindRaw) ? kindRaw : "text",
|
|
867
867
|
// A row read from the DB is at least `sent`. `sending`/`failed` are outbox
|
|
@@ -909,7 +909,7 @@ function projectConversationSummary(row, viewerId, fallbackOrganizationId) {
|
|
|
909
909
|
groupName,
|
|
910
910
|
groupImageUrl,
|
|
911
911
|
createdBy: createdBy === null ? null : createdBy,
|
|
912
|
-
organizationId: str(row, "organization_id") ?? fallbackOrganizationId,
|
|
912
|
+
organizationId: str(row, "organization_id") ?? fallbackOrganizationId ?? "",
|
|
913
913
|
createdAt,
|
|
914
914
|
updatedAt,
|
|
915
915
|
metadata: jsonObject(row["metadata"])
|
|
@@ -1242,7 +1242,11 @@ function createMessagingEngine(options) {
|
|
|
1242
1242
|
optimisticMessage({
|
|
1243
1243
|
conversationId: entry.draft.conversationId,
|
|
1244
1244
|
senderId: identity.userId,
|
|
1245
|
-
|
|
1245
|
+
// Client-only until the confirmed row replaces it: the active
|
|
1246
|
+
// organization, else the conversation's own.
|
|
1247
|
+
organizationId: identity.organizationId ?? store.snapshot().conversations.find(
|
|
1248
|
+
(item) => item.conversation.id === entry.draft.conversationId
|
|
1249
|
+
)?.conversation.organizationId ?? "",
|
|
1246
1250
|
content: entry.draft.content,
|
|
1247
1251
|
clientMessageId: entry.clientMessageId,
|
|
1248
1252
|
...entry.draft.kind !== void 0 ? { kind: entry.draft.kind } : {},
|
|
@@ -1774,12 +1778,15 @@ var RPCS = {
|
|
|
1774
1778
|
};
|
|
1775
1779
|
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";
|
|
1776
1780
|
var CONVERSATION_COLUMNS = "id,type,group_name,group_image_url,created_by,organization_id,created_at,updated_at,metadata";
|
|
1781
|
+
function optionalOrg(organizationId) {
|
|
1782
|
+
return typeof organizationId === "string" && organizationId.length > 0 ? organizationId : null;
|
|
1783
|
+
}
|
|
1777
1784
|
function requireOrg(organizationId, operation) {
|
|
1778
|
-
if (
|
|
1785
|
+
if (organizationId === null) {
|
|
1779
1786
|
throw new MessagingError(
|
|
1780
|
-
"
|
|
1781
|
-
`${operation}: no
|
|
1782
|
-
"
|
|
1787
|
+
"organization-required",
|
|
1788
|
+
`${operation}: no organization selected`,
|
|
1789
|
+
"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."
|
|
1783
1790
|
);
|
|
1784
1791
|
}
|
|
1785
1792
|
return organizationId;
|
|
@@ -1791,7 +1798,7 @@ function sleep(ms) {
|
|
|
1791
1798
|
return new Promise((resolve) => setTimeout(resolve, jittered));
|
|
1792
1799
|
}
|
|
1793
1800
|
function createMessagingRepository(options) {
|
|
1794
|
-
const org =
|
|
1801
|
+
const org = optionalOrg(options.organizationId);
|
|
1795
1802
|
const handedIn = options.identity;
|
|
1796
1803
|
if (handedIn !== void 0 && handedIn !== null && handedIn.userId !== void 0) {
|
|
1797
1804
|
options.onDiagnostic?.(
|
|
@@ -1861,6 +1868,11 @@ function createMessagingRepository(options) {
|
|
|
1861
1868
|
});
|
|
1862
1869
|
return found;
|
|
1863
1870
|
}
|
|
1871
|
+
async function organizationOfExisting(conversationId) {
|
|
1872
|
+
if (org !== null) return org;
|
|
1873
|
+
const conversation = await repository.getConversation(conversationId);
|
|
1874
|
+
return conversation.organizationId;
|
|
1875
|
+
}
|
|
1864
1876
|
const repository = {
|
|
1865
1877
|
organizationId: org,
|
|
1866
1878
|
actingUserId,
|
|
@@ -1993,6 +2005,7 @@ function createMessagingRepository(options) {
|
|
|
1993
2005
|
},
|
|
1994
2006
|
async getOrCreateDirectConversation(otherUserId) {
|
|
1995
2007
|
const operation = "getOrCreateDirectConversation";
|
|
2008
|
+
const destination = requireOrg(org, operation);
|
|
1996
2009
|
const me = await actingUser(operation);
|
|
1997
2010
|
const id = await withSessionRetry(
|
|
1998
2011
|
operation,
|
|
@@ -2004,7 +2017,7 @@ function createMessagingRepository(options) {
|
|
|
2004
2017
|
// not a preference.
|
|
2005
2018
|
p_user1_id: me,
|
|
2006
2019
|
p_user2_id: otherUserId,
|
|
2007
|
-
p_organization_id:
|
|
2020
|
+
p_organization_id: destination
|
|
2008
2021
|
},
|
|
2009
2022
|
operation
|
|
2010
2023
|
)
|
|
@@ -2016,13 +2029,14 @@ function createMessagingRepository(options) {
|
|
|
2016
2029
|
},
|
|
2017
2030
|
async createGroupConversation({ name, memberIds }) {
|
|
2018
2031
|
const operation = "createGroupConversation";
|
|
2032
|
+
const destination = requireOrg(org, operation);
|
|
2019
2033
|
const me = await actingUser(operation);
|
|
2020
2034
|
const { data, error } = await withSessionRetry(
|
|
2021
2035
|
operation,
|
|
2022
2036
|
async () => db().from(TABLES.conversations).insert({
|
|
2023
2037
|
type: "group",
|
|
2024
2038
|
group_name: name,
|
|
2025
|
-
organization_id:
|
|
2039
|
+
organization_id: destination,
|
|
2026
2040
|
created_by: me
|
|
2027
2041
|
}).select("id").single()
|
|
2028
2042
|
);
|
|
@@ -2037,7 +2051,7 @@ function createMessagingRepository(options) {
|
|
|
2037
2051
|
conversation_id: conversationId,
|
|
2038
2052
|
user_id: userId,
|
|
2039
2053
|
role: userId === me ? "owner" : "member",
|
|
2040
|
-
organization_id:
|
|
2054
|
+
organization_id: destination,
|
|
2041
2055
|
created_by: me
|
|
2042
2056
|
}))
|
|
2043
2057
|
);
|
|
@@ -2047,10 +2061,11 @@ function createMessagingRepository(options) {
|
|
|
2047
2061
|
async insertMessage(draft, clientMessageId) {
|
|
2048
2062
|
const operation = "insertMessage";
|
|
2049
2063
|
const me = await actingUser(operation);
|
|
2064
|
+
const destination = await organizationOfExisting(draft.conversationId);
|
|
2050
2065
|
const { data, error } = await db().from(TABLES.messages).insert({
|
|
2051
2066
|
conversation_id: draft.conversationId,
|
|
2052
2067
|
sender_id: me,
|
|
2053
|
-
organization_id:
|
|
2068
|
+
organization_id: destination,
|
|
2054
2069
|
created_by: me,
|
|
2055
2070
|
content: draft.content,
|
|
2056
2071
|
message_type: draft.kind ?? "text",
|
|
@@ -2111,12 +2126,13 @@ function createMessagingRepository(options) {
|
|
|
2111
2126
|
async addMembers(conversationId, memberIds) {
|
|
2112
2127
|
const operation = "addMembers";
|
|
2113
2128
|
const me = await actingUser(operation);
|
|
2129
|
+
const destination = await organizationOfExisting(conversationId);
|
|
2114
2130
|
const { error } = await db().from(TABLES.participants).insert(
|
|
2115
2131
|
memberIds.map((userId) => ({
|
|
2116
2132
|
conversation_id: conversationId,
|
|
2117
2133
|
user_id: userId,
|
|
2118
2134
|
role: "member",
|
|
2119
|
-
organization_id:
|
|
2135
|
+
organization_id: destination,
|
|
2120
2136
|
created_by: me
|
|
2121
2137
|
}))
|
|
2122
2138
|
);
|