@ai-matrx/messaging 0.11.8 → 0.12.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 +59 -0
- package/README.md +10 -3
- package/dist/index.cjs +187 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +154 -42
- package/dist/index.d.ts +154 -42
- package/dist/index.js +187 -22
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +246 -31
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +174 -50
- package/dist/react.d.ts +174 -50
- package/dist/react.js +246 -31
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -613,6 +613,45 @@ interface Outbox {
|
|
|
613
613
|
}
|
|
614
614
|
declare function createOutbox(options: OutboxOptions): Outbox;
|
|
615
615
|
|
|
616
|
+
/**
|
|
617
|
+
* ONE error classification for the whole package (C22).
|
|
618
|
+
*
|
|
619
|
+
* The banned alternative is the host catching a PostgREST error and deciding
|
|
620
|
+
* for itself whether it was an auth blip, a permission denial, or a real bug —
|
|
621
|
+
* which is how the same three-branch `if` ends up copy-pasted into every
|
|
622
|
+
* consumer and drifts. Everything this package throws is a `MessagingError`
|
|
623
|
+
* with a stable `code`, and the two that hosts genuinely treat differently
|
|
624
|
+
* (`session-unavailable`, `forbidden`) say so in the type.
|
|
625
|
+
*
|
|
626
|
+
* The incident behind `session-unavailable`: a DM reader mounted before the
|
|
627
|
+
* access token existed, every RPC came back `42501`, and the app captured 909
|
|
628
|
+
* errors in 0.6 seconds. A missing session is a NORMAL lifecycle moment — it
|
|
629
|
+
* warns and retries; it is never a red error.
|
|
630
|
+
*/
|
|
631
|
+
type MessagingErrorCode = "session-unavailable" | "contention" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "misconfigured" | "unknown";
|
|
632
|
+
declare class MessagingError extends Error {
|
|
633
|
+
readonly code: MessagingErrorCode;
|
|
634
|
+
/** The remedy. Nothing fails silently, and nothing fails without saying what to do. */
|
|
635
|
+
readonly remedy: string;
|
|
636
|
+
readonly cause?: unknown;
|
|
637
|
+
constructor(code: MessagingErrorCode, message: string, remedy: string, cause?: unknown);
|
|
638
|
+
/** True when re-running the same call is the fix. */
|
|
639
|
+
get isRetryable(): boolean;
|
|
640
|
+
}
|
|
641
|
+
interface PostgrestErrorLike {
|
|
642
|
+
message: string;
|
|
643
|
+
code?: string | undefined;
|
|
644
|
+
details?: string | null | undefined;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* THE ONE PLACE a raw PostgREST/transport error becomes a typed one. Called at
|
|
648
|
+
* every boundary in `repository.ts`; nothing else in the package inspects a raw
|
|
649
|
+
* error, and no consumer should ever have to.
|
|
650
|
+
*/
|
|
651
|
+
declare function normalizeMessagingError(error: PostgrestErrorLike | Error | unknown, operation: string): MessagingError;
|
|
652
|
+
/** A response whose SHAPE is wrong — the ingress boundary, not the wire. */
|
|
653
|
+
declare function invalidResponse(operation: string, detail: string): MessagingError;
|
|
654
|
+
|
|
616
655
|
/**
|
|
617
656
|
* THE STRUCTURAL SUPABASE SHAPE.
|
|
618
657
|
*
|
|
@@ -715,6 +754,95 @@ interface MessagingSupabaseClient {
|
|
|
715
754
|
/** The internal client contract — what the repository and the manager see. */
|
|
716
755
|
type MessagingSupabaseInternal = SupabaseLike & RealtimeClientLike;
|
|
717
756
|
|
|
757
|
+
/**
|
|
758
|
+
* WHO IS ACTING — read from the session, never handed in (DD-241).
|
|
759
|
+
*
|
|
760
|
+
* THE CLASS THIS CLOSES, and why messaging is the WORSE half of it. Through
|
|
761
|
+
* 0.11.x this package took the acting user's id from whoever built it:
|
|
762
|
+
* `<MessagingProvider userId>` for the React path, and
|
|
763
|
+
* `createMessagingRepository({ identity: { userId } })` for the framework-free
|
|
764
|
+
* one. Both ends got their id the same way in the only host that exists —
|
|
765
|
+
* matrx-frontend's Redux `selectUserId` — and that copy is written once, when
|
|
766
|
+
* the app boots. Nothing rewrites it when the domain-wide auth cookie rotates
|
|
767
|
+
* to another account, when the person signs out in another tab, or when a
|
|
768
|
+
* refresh returns a different subject.
|
|
769
|
+
*
|
|
770
|
+
* Meet's version of this (DD-240, `@ai-matrx/meet` 0.6.0) made READS fail:
|
|
771
|
+
*
|
|
772
|
+
* `communication.meet_pending_call_invites(p_user_id => <the stale copy>)`
|
|
773
|
+
* → 42501 at HTTP 403, "meet: the acting user (4cf62e4e…) is not the
|
|
774
|
+
* authenticated user"
|
|
775
|
+
*
|
|
776
|
+
* Messaging's version reaches further, because here the id is not only a read
|
|
777
|
+
* argument — it is a WRITE argument. `identity.userId` lands on the wire as
|
|
778
|
+
* `p_user1_id` (the direct-conversation pair), `sender_id` (who a message is
|
|
779
|
+
* FROM), `created_by` and `updated_by` (the audit columns), and
|
|
780
|
+
* `.eq("user_id", …)` (whose participant row is being read and updated). A
|
|
781
|
+
* stale one does not merely get refused: where RLS lets it through it writes a
|
|
782
|
+
* row attributed to somebody the tab cannot prove it is.
|
|
783
|
+
*
|
|
784
|
+
* So there is ONE source now, and it is the client's own session. The same
|
|
785
|
+
* client that mints the JWT on every PostgREST request answers "who is this?",
|
|
786
|
+
* so the id in the argument and the id in the token cannot disagree — not
|
|
787
|
+
* because they are kept in step, but because they are the same fact read once.
|
|
788
|
+
*
|
|
789
|
+
* WHAT THIS MODULE IS. A tiny observable over `client.auth`: the current acting
|
|
790
|
+
* user id, a subscription that fires when it changes, and a `resolving` state
|
|
791
|
+
* that is honest about the moment before the first `getSession()` answers.
|
|
792
|
+
*
|
|
793
|
+
* - It is `resolving` until the first read lands. Nothing user-scoped is
|
|
794
|
+
* issued in that window, which is what a page-load race used to spend
|
|
795
|
+
* issuing reads and writes as a user the tab could not prove it was.
|
|
796
|
+
* - `snapshot()` returns THE SAME REFERENCE until the acting user moves — a
|
|
797
|
+
* `useSyncExternalStore` source that returns a fresh object per call is
|
|
798
|
+
* "Maximum update depth exceeded", not a warning.
|
|
799
|
+
* - It NEVER refuses anything on its own authority. It reports who the session
|
|
800
|
+
* says you are; the database remains the only thing that decides what that
|
|
801
|
+
* is allowed to read or write.
|
|
802
|
+
* - A client with no `auth` at all is a real, named failure with a remedy —
|
|
803
|
+
* never a silent fall back to "nobody".
|
|
804
|
+
*
|
|
805
|
+
* THE CLIENT BOUNDARY STAYS SHALLOW (0.2.1, `repository.ts`'s header). The
|
|
806
|
+
* public prop type is still `MessagingSupabaseClient`, which does not describe
|
|
807
|
+
* `auth` at all — so this module narrows to its own minimal `AuthLike` in ONE
|
|
808
|
+
* place, exactly as `createMessagingRepository` narrows to
|
|
809
|
+
* `MessagingSupabaseInternal`. A real `SupabaseClient` satisfying `AuthLike` is
|
|
810
|
+
* proven at compile time in `supabase-shape.test.ts`.
|
|
811
|
+
*/
|
|
812
|
+
|
|
813
|
+
interface SessionIdentitySnapshot {
|
|
814
|
+
/**
|
|
815
|
+
* `resolving` until the first `getSession()` answers. A user-scoped read or
|
|
816
|
+
* write issued in this window would name a guess.
|
|
817
|
+
*/
|
|
818
|
+
readonly state: "resolving" | "resolved";
|
|
819
|
+
/** Who the session says is acting, or `null` for nobody. */
|
|
820
|
+
readonly userId: UserId | null;
|
|
821
|
+
}
|
|
822
|
+
interface SessionIdentity {
|
|
823
|
+
/**
|
|
824
|
+
* False when the client handed to us has no auth source at all — a
|
|
825
|
+
* misconfiguration, not "nobody is signed in", and the two get different
|
|
826
|
+
* sentences.
|
|
827
|
+
*/
|
|
828
|
+
readonly hasAuthSource: boolean;
|
|
829
|
+
/** Stable reference until the acting user actually changes. */
|
|
830
|
+
snapshot(): SessionIdentitySnapshot;
|
|
831
|
+
subscribe(listener: () => void): () => void;
|
|
832
|
+
dispose(): void;
|
|
833
|
+
}
|
|
834
|
+
interface SessionIdentityOptions {
|
|
835
|
+
readonly client: MessagingSupabaseClient;
|
|
836
|
+
readonly onDiagnostic?: ((event: {
|
|
837
|
+
level: "info" | "warn" | "error";
|
|
838
|
+
message: string;
|
|
839
|
+
remedy?: string | undefined;
|
|
840
|
+
}) => void) | undefined;
|
|
841
|
+
}
|
|
842
|
+
/** The snapshot every server render sees: nobody is acting on a server. */
|
|
843
|
+
declare const SERVER_SESSION_IDENTITY: SessionIdentitySnapshot;
|
|
844
|
+
declare function createSessionIdentity(options: SessionIdentityOptions): SessionIdentity;
|
|
845
|
+
|
|
718
846
|
/**
|
|
719
847
|
* THE DATA CONTRACT — the ONE place a messaging table or RPC name exists.
|
|
720
848
|
*
|
|
@@ -791,7 +919,23 @@ interface RepositoryOptions {
|
|
|
791
919
|
* the provider does.
|
|
792
920
|
*/
|
|
793
921
|
client: MessagingSupabaseClient;
|
|
794
|
-
|
|
922
|
+
/**
|
|
923
|
+
* The active organization. Every conversation and message write carries it
|
|
924
|
+
* explicitly (R5).
|
|
925
|
+
*
|
|
926
|
+
* 🚨 THERE IS NO `userId` HERE, AND THERE WILL NOT BE ONE (DD-241). Who is
|
|
927
|
+
* acting is read from `client`'s own session — see `session-identity.ts` for
|
|
928
|
+
* the class that closed and why a handed-in id could not be made safe by
|
|
929
|
+
* convention. A caller that still passes `identity` is IGNORED and told so.
|
|
930
|
+
*/
|
|
931
|
+
organizationId: string;
|
|
932
|
+
/**
|
|
933
|
+
* INTERNAL. The React provider already runs one session reader for its own
|
|
934
|
+
* render loop and hands it here so the two cannot answer differently and so
|
|
935
|
+
* one `onAuthStateChange` subscription serves both. A framework-free caller
|
|
936
|
+
* omits it and the repository builds its own from `client`.
|
|
937
|
+
*/
|
|
938
|
+
sessionIdentity?: SessionIdentity | undefined;
|
|
795
939
|
/** Called once before a retry when a read fails with a missing session. */
|
|
796
940
|
resolveSession?: SessionResolver | undefined;
|
|
797
941
|
/**
|
|
@@ -805,7 +949,14 @@ interface RepositoryOptions {
|
|
|
805
949
|
now?: (() => number) | undefined;
|
|
806
950
|
}
|
|
807
951
|
interface MessagingRepository {
|
|
808
|
-
|
|
952
|
+
/** The organization every write here is stamped with. */
|
|
953
|
+
readonly organizationId: OrganizationId;
|
|
954
|
+
/**
|
|
955
|
+
* Who the session says is acting — awaited, because the first `getSession()`
|
|
956
|
+
* is a real read. Rejects with `session-unavailable` when nobody is signed
|
|
957
|
+
* in, which is the code the retry loop already knows how to heal.
|
|
958
|
+
*/
|
|
959
|
+
actingUserId(): Promise<UserId>;
|
|
809
960
|
listConversations(args?: {
|
|
810
961
|
limit?: number;
|
|
811
962
|
cursor?: ConversationCursor | null;
|
|
@@ -1068,45 +1219,6 @@ declare function createMessagingEngine(options: MessagingEngineOptions): Messagi
|
|
|
1068
1219
|
/** The realtime client session id, exposed for diagnostics surfaces. */
|
|
1069
1220
|
declare function messagingClientId(): string;
|
|
1070
1221
|
|
|
1071
|
-
/**
|
|
1072
|
-
* ONE error classification for the whole package (C22).
|
|
1073
|
-
*
|
|
1074
|
-
* The banned alternative is the host catching a PostgREST error and deciding
|
|
1075
|
-
* for itself whether it was an auth blip, a permission denial, or a real bug —
|
|
1076
|
-
* which is how the same three-branch `if` ends up copy-pasted into every
|
|
1077
|
-
* consumer and drifts. Everything this package throws is a `MessagingError`
|
|
1078
|
-
* with a stable `code`, and the two that hosts genuinely treat differently
|
|
1079
|
-
* (`session-unavailable`, `forbidden`) say so in the type.
|
|
1080
|
-
*
|
|
1081
|
-
* The incident behind `session-unavailable`: a DM reader mounted before the
|
|
1082
|
-
* access token existed, every RPC came back `42501`, and the app captured 909
|
|
1083
|
-
* errors in 0.6 seconds. A missing session is a NORMAL lifecycle moment — it
|
|
1084
|
-
* warns and retries; it is never a red error.
|
|
1085
|
-
*/
|
|
1086
|
-
type MessagingErrorCode = "session-unavailable" | "contention" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "misconfigured" | "unknown";
|
|
1087
|
-
declare class MessagingError extends Error {
|
|
1088
|
-
readonly code: MessagingErrorCode;
|
|
1089
|
-
/** The remedy. Nothing fails silently, and nothing fails without saying what to do. */
|
|
1090
|
-
readonly remedy: string;
|
|
1091
|
-
readonly cause?: unknown;
|
|
1092
|
-
constructor(code: MessagingErrorCode, message: string, remedy: string, cause?: unknown);
|
|
1093
|
-
/** True when re-running the same call is the fix. */
|
|
1094
|
-
get isRetryable(): boolean;
|
|
1095
|
-
}
|
|
1096
|
-
interface PostgrestErrorLike {
|
|
1097
|
-
message: string;
|
|
1098
|
-
code?: string | undefined;
|
|
1099
|
-
details?: string | null | undefined;
|
|
1100
|
-
}
|
|
1101
|
-
/**
|
|
1102
|
-
* THE ONE PLACE a raw PostgREST/transport error becomes a typed one. Called at
|
|
1103
|
-
* every boundary in `repository.ts`; nothing else in the package inspects a raw
|
|
1104
|
-
* error, and no consumer should ever have to.
|
|
1105
|
-
*/
|
|
1106
|
-
declare function normalizeMessagingError(error: PostgrestErrorLike | Error | unknown, operation: string): MessagingError;
|
|
1107
|
-
/** A response whose SHAPE is wrong — the ingress boundary, not the wire. */
|
|
1108
|
-
declare function invalidResponse(operation: string, detail: string): MessagingError;
|
|
1109
|
-
|
|
1110
1222
|
/**
|
|
1111
1223
|
* Display formatting. In the package because every consumer otherwise rebuilds
|
|
1112
1224
|
* it slightly differently and the inbox and the thread disagree about what
|
|
@@ -1237,4 +1349,4 @@ declare function summarizeText(content: string, maxLength?: number): string;
|
|
|
1237
1349
|
/** Serialize picked references into a fence the platform's other readers accept. */
|
|
1238
1350
|
declare function composeFence(references: readonly MatrxReference[]): string;
|
|
1239
1351
|
|
|
1240
|
-
export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, type AiCallArgs, type AiCapability, type AiResult, type Attachment, type ClientMessageId, type Conversation, type ConversationCursor, type ConversationId, type ConversationSummary, type ConversationThread, type ConversationType, DEFAULT_MESSAGING_ARCHIVE_FILTER, type DeliveryState, type DraftMessage, type EngineDiagnostic, type JsonObject, type JsonValue, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessagingAgentIdentity, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingArchiveFilter, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingIdentity, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type MessagingSupabaseClient, type MessagingSupabaseInternal, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, type Participant, type ParticipantRole, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, type RepositoryOptions, type SchemaLike, type SessionResolver, type SupabaseLike, TABLES, type TextSegment, type UserId, type UserSummary, asClientMessageId, asConversationId, asMessageId, asOrganizationId, asUserId, composeFence, conversationTopic, createActionRegistry, createMemoryOutboxStorage, createMessagingAi, createMessagingEngine, createMessagingRepository, createMessagingStore, createOutbox, createReadCache, createWebOutboxStorage, effectiveActorKey, extractReferences, formatConversationTime, formatDateSeparator, formatLastSeen, formatMessageTime, formatTypists, groupMessages, inboxTopic, invalidResponse, isSameDay, messagingClientId, normalizeMessagingError, optimisticMessage, participantNames, projectConversationSummary, projectMessage, projectMessageAction, projectParticipantRole, projectUserSummary, resolveActor, splitText, summarizeText, toMessagingArchiveFilter, unreadCutoff };
|
|
1352
|
+
export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, type AiCallArgs, type AiCapability, type AiResult, type Attachment, type ClientMessageId, type Conversation, type ConversationCursor, type ConversationId, type ConversationSummary, type ConversationThread, type ConversationType, DEFAULT_MESSAGING_ARCHIVE_FILTER, type DeliveryState, type DraftMessage, type EngineDiagnostic, type JsonObject, type JsonValue, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessagingAgentIdentity, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingArchiveFilter, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingIdentity, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type MessagingSupabaseClient, type MessagingSupabaseInternal, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, type Participant, type ParticipantRole, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, type RepositoryOptions, SERVER_SESSION_IDENTITY, type SchemaLike, type SessionIdentity, type SessionIdentitySnapshot, type SessionResolver, type SupabaseLike, TABLES, type TextSegment, type UserId, type UserSummary, asClientMessageId, asConversationId, asMessageId, asOrganizationId, asUserId, composeFence, conversationTopic, createActionRegistry, createMemoryOutboxStorage, createMessagingAi, createMessagingEngine, createMessagingRepository, createMessagingStore, createOutbox, createReadCache, createSessionIdentity, createWebOutboxStorage, effectiveActorKey, extractReferences, formatConversationTime, formatDateSeparator, formatLastSeen, formatMessageTime, formatTypists, groupMessages, inboxTopic, invalidResponse, isSameDay, messagingClientId, normalizeMessagingError, optimisticMessage, participantNames, projectConversationSummary, projectMessage, projectMessageAction, projectParticipantRole, projectUserSummary, resolveActor, splitText, summarizeText, toMessagingArchiveFilter, unreadCutoff };
|
package/dist/index.d.ts
CHANGED
|
@@ -613,6 +613,45 @@ interface Outbox {
|
|
|
613
613
|
}
|
|
614
614
|
declare function createOutbox(options: OutboxOptions): Outbox;
|
|
615
615
|
|
|
616
|
+
/**
|
|
617
|
+
* ONE error classification for the whole package (C22).
|
|
618
|
+
*
|
|
619
|
+
* The banned alternative is the host catching a PostgREST error and deciding
|
|
620
|
+
* for itself whether it was an auth blip, a permission denial, or a real bug —
|
|
621
|
+
* which is how the same three-branch `if` ends up copy-pasted into every
|
|
622
|
+
* consumer and drifts. Everything this package throws is a `MessagingError`
|
|
623
|
+
* with a stable `code`, and the two that hosts genuinely treat differently
|
|
624
|
+
* (`session-unavailable`, `forbidden`) say so in the type.
|
|
625
|
+
*
|
|
626
|
+
* The incident behind `session-unavailable`: a DM reader mounted before the
|
|
627
|
+
* access token existed, every RPC came back `42501`, and the app captured 909
|
|
628
|
+
* errors in 0.6 seconds. A missing session is a NORMAL lifecycle moment — it
|
|
629
|
+
* warns and retries; it is never a red error.
|
|
630
|
+
*/
|
|
631
|
+
type MessagingErrorCode = "session-unavailable" | "contention" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "misconfigured" | "unknown";
|
|
632
|
+
declare class MessagingError extends Error {
|
|
633
|
+
readonly code: MessagingErrorCode;
|
|
634
|
+
/** The remedy. Nothing fails silently, and nothing fails without saying what to do. */
|
|
635
|
+
readonly remedy: string;
|
|
636
|
+
readonly cause?: unknown;
|
|
637
|
+
constructor(code: MessagingErrorCode, message: string, remedy: string, cause?: unknown);
|
|
638
|
+
/** True when re-running the same call is the fix. */
|
|
639
|
+
get isRetryable(): boolean;
|
|
640
|
+
}
|
|
641
|
+
interface PostgrestErrorLike {
|
|
642
|
+
message: string;
|
|
643
|
+
code?: string | undefined;
|
|
644
|
+
details?: string | null | undefined;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* THE ONE PLACE a raw PostgREST/transport error becomes a typed one. Called at
|
|
648
|
+
* every boundary in `repository.ts`; nothing else in the package inspects a raw
|
|
649
|
+
* error, and no consumer should ever have to.
|
|
650
|
+
*/
|
|
651
|
+
declare function normalizeMessagingError(error: PostgrestErrorLike | Error | unknown, operation: string): MessagingError;
|
|
652
|
+
/** A response whose SHAPE is wrong — the ingress boundary, not the wire. */
|
|
653
|
+
declare function invalidResponse(operation: string, detail: string): MessagingError;
|
|
654
|
+
|
|
616
655
|
/**
|
|
617
656
|
* THE STRUCTURAL SUPABASE SHAPE.
|
|
618
657
|
*
|
|
@@ -715,6 +754,95 @@ interface MessagingSupabaseClient {
|
|
|
715
754
|
/** The internal client contract — what the repository and the manager see. */
|
|
716
755
|
type MessagingSupabaseInternal = SupabaseLike & RealtimeClientLike;
|
|
717
756
|
|
|
757
|
+
/**
|
|
758
|
+
* WHO IS ACTING — read from the session, never handed in (DD-241).
|
|
759
|
+
*
|
|
760
|
+
* THE CLASS THIS CLOSES, and why messaging is the WORSE half of it. Through
|
|
761
|
+
* 0.11.x this package took the acting user's id from whoever built it:
|
|
762
|
+
* `<MessagingProvider userId>` for the React path, and
|
|
763
|
+
* `createMessagingRepository({ identity: { userId } })` for the framework-free
|
|
764
|
+
* one. Both ends got their id the same way in the only host that exists —
|
|
765
|
+
* matrx-frontend's Redux `selectUserId` — and that copy is written once, when
|
|
766
|
+
* the app boots. Nothing rewrites it when the domain-wide auth cookie rotates
|
|
767
|
+
* to another account, when the person signs out in another tab, or when a
|
|
768
|
+
* refresh returns a different subject.
|
|
769
|
+
*
|
|
770
|
+
* Meet's version of this (DD-240, `@ai-matrx/meet` 0.6.0) made READS fail:
|
|
771
|
+
*
|
|
772
|
+
* `communication.meet_pending_call_invites(p_user_id => <the stale copy>)`
|
|
773
|
+
* → 42501 at HTTP 403, "meet: the acting user (4cf62e4e…) is not the
|
|
774
|
+
* authenticated user"
|
|
775
|
+
*
|
|
776
|
+
* Messaging's version reaches further, because here the id is not only a read
|
|
777
|
+
* argument — it is a WRITE argument. `identity.userId` lands on the wire as
|
|
778
|
+
* `p_user1_id` (the direct-conversation pair), `sender_id` (who a message is
|
|
779
|
+
* FROM), `created_by` and `updated_by` (the audit columns), and
|
|
780
|
+
* `.eq("user_id", …)` (whose participant row is being read and updated). A
|
|
781
|
+
* stale one does not merely get refused: where RLS lets it through it writes a
|
|
782
|
+
* row attributed to somebody the tab cannot prove it is.
|
|
783
|
+
*
|
|
784
|
+
* So there is ONE source now, and it is the client's own session. The same
|
|
785
|
+
* client that mints the JWT on every PostgREST request answers "who is this?",
|
|
786
|
+
* so the id in the argument and the id in the token cannot disagree — not
|
|
787
|
+
* because they are kept in step, but because they are the same fact read once.
|
|
788
|
+
*
|
|
789
|
+
* WHAT THIS MODULE IS. A tiny observable over `client.auth`: the current acting
|
|
790
|
+
* user id, a subscription that fires when it changes, and a `resolving` state
|
|
791
|
+
* that is honest about the moment before the first `getSession()` answers.
|
|
792
|
+
*
|
|
793
|
+
* - It is `resolving` until the first read lands. Nothing user-scoped is
|
|
794
|
+
* issued in that window, which is what a page-load race used to spend
|
|
795
|
+
* issuing reads and writes as a user the tab could not prove it was.
|
|
796
|
+
* - `snapshot()` returns THE SAME REFERENCE until the acting user moves — a
|
|
797
|
+
* `useSyncExternalStore` source that returns a fresh object per call is
|
|
798
|
+
* "Maximum update depth exceeded", not a warning.
|
|
799
|
+
* - It NEVER refuses anything on its own authority. It reports who the session
|
|
800
|
+
* says you are; the database remains the only thing that decides what that
|
|
801
|
+
* is allowed to read or write.
|
|
802
|
+
* - A client with no `auth` at all is a real, named failure with a remedy —
|
|
803
|
+
* never a silent fall back to "nobody".
|
|
804
|
+
*
|
|
805
|
+
* THE CLIENT BOUNDARY STAYS SHALLOW (0.2.1, `repository.ts`'s header). The
|
|
806
|
+
* public prop type is still `MessagingSupabaseClient`, which does not describe
|
|
807
|
+
* `auth` at all — so this module narrows to its own minimal `AuthLike` in ONE
|
|
808
|
+
* place, exactly as `createMessagingRepository` narrows to
|
|
809
|
+
* `MessagingSupabaseInternal`. A real `SupabaseClient` satisfying `AuthLike` is
|
|
810
|
+
* proven at compile time in `supabase-shape.test.ts`.
|
|
811
|
+
*/
|
|
812
|
+
|
|
813
|
+
interface SessionIdentitySnapshot {
|
|
814
|
+
/**
|
|
815
|
+
* `resolving` until the first `getSession()` answers. A user-scoped read or
|
|
816
|
+
* write issued in this window would name a guess.
|
|
817
|
+
*/
|
|
818
|
+
readonly state: "resolving" | "resolved";
|
|
819
|
+
/** Who the session says is acting, or `null` for nobody. */
|
|
820
|
+
readonly userId: UserId | null;
|
|
821
|
+
}
|
|
822
|
+
interface SessionIdentity {
|
|
823
|
+
/**
|
|
824
|
+
* False when the client handed to us has no auth source at all — a
|
|
825
|
+
* misconfiguration, not "nobody is signed in", and the two get different
|
|
826
|
+
* sentences.
|
|
827
|
+
*/
|
|
828
|
+
readonly hasAuthSource: boolean;
|
|
829
|
+
/** Stable reference until the acting user actually changes. */
|
|
830
|
+
snapshot(): SessionIdentitySnapshot;
|
|
831
|
+
subscribe(listener: () => void): () => void;
|
|
832
|
+
dispose(): void;
|
|
833
|
+
}
|
|
834
|
+
interface SessionIdentityOptions {
|
|
835
|
+
readonly client: MessagingSupabaseClient;
|
|
836
|
+
readonly onDiagnostic?: ((event: {
|
|
837
|
+
level: "info" | "warn" | "error";
|
|
838
|
+
message: string;
|
|
839
|
+
remedy?: string | undefined;
|
|
840
|
+
}) => void) | undefined;
|
|
841
|
+
}
|
|
842
|
+
/** The snapshot every server render sees: nobody is acting on a server. */
|
|
843
|
+
declare const SERVER_SESSION_IDENTITY: SessionIdentitySnapshot;
|
|
844
|
+
declare function createSessionIdentity(options: SessionIdentityOptions): SessionIdentity;
|
|
845
|
+
|
|
718
846
|
/**
|
|
719
847
|
* THE DATA CONTRACT — the ONE place a messaging table or RPC name exists.
|
|
720
848
|
*
|
|
@@ -791,7 +919,23 @@ interface RepositoryOptions {
|
|
|
791
919
|
* the provider does.
|
|
792
920
|
*/
|
|
793
921
|
client: MessagingSupabaseClient;
|
|
794
|
-
|
|
922
|
+
/**
|
|
923
|
+
* The active organization. Every conversation and message write carries it
|
|
924
|
+
* explicitly (R5).
|
|
925
|
+
*
|
|
926
|
+
* 🚨 THERE IS NO `userId` HERE, AND THERE WILL NOT BE ONE (DD-241). Who is
|
|
927
|
+
* acting is read from `client`'s own session — see `session-identity.ts` for
|
|
928
|
+
* the class that closed and why a handed-in id could not be made safe by
|
|
929
|
+
* convention. A caller that still passes `identity` is IGNORED and told so.
|
|
930
|
+
*/
|
|
931
|
+
organizationId: string;
|
|
932
|
+
/**
|
|
933
|
+
* INTERNAL. The React provider already runs one session reader for its own
|
|
934
|
+
* render loop and hands it here so the two cannot answer differently and so
|
|
935
|
+
* one `onAuthStateChange` subscription serves both. A framework-free caller
|
|
936
|
+
* omits it and the repository builds its own from `client`.
|
|
937
|
+
*/
|
|
938
|
+
sessionIdentity?: SessionIdentity | undefined;
|
|
795
939
|
/** Called once before a retry when a read fails with a missing session. */
|
|
796
940
|
resolveSession?: SessionResolver | undefined;
|
|
797
941
|
/**
|
|
@@ -805,7 +949,14 @@ interface RepositoryOptions {
|
|
|
805
949
|
now?: (() => number) | undefined;
|
|
806
950
|
}
|
|
807
951
|
interface MessagingRepository {
|
|
808
|
-
|
|
952
|
+
/** The organization every write here is stamped with. */
|
|
953
|
+
readonly organizationId: OrganizationId;
|
|
954
|
+
/**
|
|
955
|
+
* Who the session says is acting — awaited, because the first `getSession()`
|
|
956
|
+
* is a real read. Rejects with `session-unavailable` when nobody is signed
|
|
957
|
+
* in, which is the code the retry loop already knows how to heal.
|
|
958
|
+
*/
|
|
959
|
+
actingUserId(): Promise<UserId>;
|
|
809
960
|
listConversations(args?: {
|
|
810
961
|
limit?: number;
|
|
811
962
|
cursor?: ConversationCursor | null;
|
|
@@ -1068,45 +1219,6 @@ declare function createMessagingEngine(options: MessagingEngineOptions): Messagi
|
|
|
1068
1219
|
/** The realtime client session id, exposed for diagnostics surfaces. */
|
|
1069
1220
|
declare function messagingClientId(): string;
|
|
1070
1221
|
|
|
1071
|
-
/**
|
|
1072
|
-
* ONE error classification for the whole package (C22).
|
|
1073
|
-
*
|
|
1074
|
-
* The banned alternative is the host catching a PostgREST error and deciding
|
|
1075
|
-
* for itself whether it was an auth blip, a permission denial, or a real bug —
|
|
1076
|
-
* which is how the same three-branch `if` ends up copy-pasted into every
|
|
1077
|
-
* consumer and drifts. Everything this package throws is a `MessagingError`
|
|
1078
|
-
* with a stable `code`, and the two that hosts genuinely treat differently
|
|
1079
|
-
* (`session-unavailable`, `forbidden`) say so in the type.
|
|
1080
|
-
*
|
|
1081
|
-
* The incident behind `session-unavailable`: a DM reader mounted before the
|
|
1082
|
-
* access token existed, every RPC came back `42501`, and the app captured 909
|
|
1083
|
-
* errors in 0.6 seconds. A missing session is a NORMAL lifecycle moment — it
|
|
1084
|
-
* warns and retries; it is never a red error.
|
|
1085
|
-
*/
|
|
1086
|
-
type MessagingErrorCode = "session-unavailable" | "contention" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "misconfigured" | "unknown";
|
|
1087
|
-
declare class MessagingError extends Error {
|
|
1088
|
-
readonly code: MessagingErrorCode;
|
|
1089
|
-
/** The remedy. Nothing fails silently, and nothing fails without saying what to do. */
|
|
1090
|
-
readonly remedy: string;
|
|
1091
|
-
readonly cause?: unknown;
|
|
1092
|
-
constructor(code: MessagingErrorCode, message: string, remedy: string, cause?: unknown);
|
|
1093
|
-
/** True when re-running the same call is the fix. */
|
|
1094
|
-
get isRetryable(): boolean;
|
|
1095
|
-
}
|
|
1096
|
-
interface PostgrestErrorLike {
|
|
1097
|
-
message: string;
|
|
1098
|
-
code?: string | undefined;
|
|
1099
|
-
details?: string | null | undefined;
|
|
1100
|
-
}
|
|
1101
|
-
/**
|
|
1102
|
-
* THE ONE PLACE a raw PostgREST/transport error becomes a typed one. Called at
|
|
1103
|
-
* every boundary in `repository.ts`; nothing else in the package inspects a raw
|
|
1104
|
-
* error, and no consumer should ever have to.
|
|
1105
|
-
*/
|
|
1106
|
-
declare function normalizeMessagingError(error: PostgrestErrorLike | Error | unknown, operation: string): MessagingError;
|
|
1107
|
-
/** A response whose SHAPE is wrong — the ingress boundary, not the wire. */
|
|
1108
|
-
declare function invalidResponse(operation: string, detail: string): MessagingError;
|
|
1109
|
-
|
|
1110
1222
|
/**
|
|
1111
1223
|
* Display formatting. In the package because every consumer otherwise rebuilds
|
|
1112
1224
|
* it slightly differently and the inbox and the thread disagree about what
|
|
@@ -1237,4 +1349,4 @@ declare function summarizeText(content: string, maxLength?: number): string;
|
|
|
1237
1349
|
/** Serialize picked references into a fence the platform's other readers accept. */
|
|
1238
1350
|
declare function composeFence(references: readonly MatrxReference[]): string;
|
|
1239
1351
|
|
|
1240
|
-
export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, type AiCallArgs, type AiCapability, type AiResult, type Attachment, type ClientMessageId, type Conversation, type ConversationCursor, type ConversationId, type ConversationSummary, type ConversationThread, type ConversationType, DEFAULT_MESSAGING_ARCHIVE_FILTER, type DeliveryState, type DraftMessage, type EngineDiagnostic, type JsonObject, type JsonValue, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessagingAgentIdentity, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingArchiveFilter, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingIdentity, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type MessagingSupabaseClient, type MessagingSupabaseInternal, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, type Participant, type ParticipantRole, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, type RepositoryOptions, type SchemaLike, type SessionResolver, type SupabaseLike, TABLES, type TextSegment, type UserId, type UserSummary, asClientMessageId, asConversationId, asMessageId, asOrganizationId, asUserId, composeFence, conversationTopic, createActionRegistry, createMemoryOutboxStorage, createMessagingAi, createMessagingEngine, createMessagingRepository, createMessagingStore, createOutbox, createReadCache, createWebOutboxStorage, effectiveActorKey, extractReferences, formatConversationTime, formatDateSeparator, formatLastSeen, formatMessageTime, formatTypists, groupMessages, inboxTopic, invalidResponse, isSameDay, messagingClientId, normalizeMessagingError, optimisticMessage, participantNames, projectConversationSummary, projectMessage, projectMessageAction, projectParticipantRole, projectUserSummary, resolveActor, splitText, summarizeText, toMessagingArchiveFilter, unreadCutoff };
|
|
1352
|
+
export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, type AiCallArgs, type AiCapability, type AiResult, type Attachment, type ClientMessageId, type Conversation, type ConversationCursor, type ConversationId, type ConversationSummary, type ConversationThread, type ConversationType, DEFAULT_MESSAGING_ARCHIVE_FILTER, type DeliveryState, type DraftMessage, type EngineDiagnostic, type JsonObject, type JsonValue, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessagingAgentIdentity, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingArchiveFilter, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingIdentity, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type MessagingSupabaseClient, type MessagingSupabaseInternal, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, type Participant, type ParticipantRole, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, type RepositoryOptions, SERVER_SESSION_IDENTITY, type SchemaLike, type SessionIdentity, type SessionIdentitySnapshot, type SessionResolver, type SupabaseLike, TABLES, type TextSegment, type UserId, type UserSummary, asClientMessageId, asConversationId, asMessageId, asOrganizationId, asUserId, composeFence, conversationTopic, createActionRegistry, createMemoryOutboxStorage, createMessagingAi, createMessagingEngine, createMessagingRepository, createMessagingStore, createOutbox, createReadCache, createSessionIdentity, createWebOutboxStorage, effectiveActorKey, extractReferences, formatConversationTime, formatDateSeparator, formatLastSeen, formatMessageTime, formatTypists, groupMessages, inboxTopic, invalidResponse, isSameDay, messagingClientId, normalizeMessagingError, optimisticMessage, participantNames, projectConversationSummary, projectMessage, projectMessageAction, projectParticipantRole, projectUserSummary, resolveActor, splitText, summarizeText, toMessagingArchiveFilter, unreadCutoff };
|