@ai-matrx/messaging 0.11.1 → 0.11.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/CHANGELOG.md +45 -0
- package/dist/index.cjs +42 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -2
- package/dist/index.d.ts +8 -2
- package/dist/index.js +42 -14
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +46 -14
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +8 -2
- package/dist/react.d.ts +8 -2
- package/dist/react.js +46 -14
- package/dist/react.js.map +1 -1
- package/dist/styles.css +9 -0
- package/package.json +3 -3
package/dist/react.d.cts
CHANGED
|
@@ -691,6 +691,12 @@ interface RepositoryOptions {
|
|
|
691
691
|
identity: MessagingIdentity;
|
|
692
692
|
/** Called once before a retry when a read fails with a missing session. */
|
|
693
693
|
resolveSession?: SessionResolver | undefined;
|
|
694
|
+
/**
|
|
695
|
+
* Announced automatic interventions. Nothing fails silently and nothing heals
|
|
696
|
+
* silently either: every lock-collision retry below reports here (the React
|
|
697
|
+
* provider wires it to the host's diagnostics as a warning).
|
|
698
|
+
*/
|
|
699
|
+
onDiagnostic?: ((message: string) => void) | undefined;
|
|
694
700
|
/** Profile-lookup cache TTL. Default 5 minutes (the frontend's proven value). */
|
|
695
701
|
userTtlMs?: number | undefined;
|
|
696
702
|
now?: (() => number) | undefined;
|
|
@@ -1521,14 +1527,14 @@ declare const MESSAGING_EVENTS: {
|
|
|
1521
1527
|
* errors in 0.6 seconds. A missing session is a NORMAL lifecycle moment — it
|
|
1522
1528
|
* warns and retries; it is never a red error.
|
|
1523
1529
|
*/
|
|
1524
|
-
type MessagingErrorCode = "session-unavailable" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "misconfigured" | "unknown";
|
|
1530
|
+
type MessagingErrorCode = "session-unavailable" | "contention" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "misconfigured" | "unknown";
|
|
1525
1531
|
declare class MessagingError extends Error {
|
|
1526
1532
|
readonly code: MessagingErrorCode;
|
|
1527
1533
|
/** The remedy. Nothing fails silently, and nothing fails without saying what to do. */
|
|
1528
1534
|
readonly remedy: string;
|
|
1529
1535
|
readonly cause?: unknown;
|
|
1530
1536
|
constructor(code: MessagingErrorCode, message: string, remedy: string, cause?: unknown);
|
|
1531
|
-
/** True when
|
|
1537
|
+
/** True when re-running the same call is the fix. */
|
|
1532
1538
|
get isRetryable(): boolean;
|
|
1533
1539
|
}
|
|
1534
1540
|
interface PostgrestErrorLike {
|
package/dist/react.d.ts
CHANGED
|
@@ -691,6 +691,12 @@ interface RepositoryOptions {
|
|
|
691
691
|
identity: MessagingIdentity;
|
|
692
692
|
/** Called once before a retry when a read fails with a missing session. */
|
|
693
693
|
resolveSession?: SessionResolver | undefined;
|
|
694
|
+
/**
|
|
695
|
+
* Announced automatic interventions. Nothing fails silently and nothing heals
|
|
696
|
+
* silently either: every lock-collision retry below reports here (the React
|
|
697
|
+
* provider wires it to the host's diagnostics as a warning).
|
|
698
|
+
*/
|
|
699
|
+
onDiagnostic?: ((message: string) => void) | undefined;
|
|
694
700
|
/** Profile-lookup cache TTL. Default 5 minutes (the frontend's proven value). */
|
|
695
701
|
userTtlMs?: number | undefined;
|
|
696
702
|
now?: (() => number) | undefined;
|
|
@@ -1521,14 +1527,14 @@ declare const MESSAGING_EVENTS: {
|
|
|
1521
1527
|
* errors in 0.6 seconds. A missing session is a NORMAL lifecycle moment — it
|
|
1522
1528
|
* warns and retries; it is never a red error.
|
|
1523
1529
|
*/
|
|
1524
|
-
type MessagingErrorCode = "session-unavailable" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "misconfigured" | "unknown";
|
|
1530
|
+
type MessagingErrorCode = "session-unavailable" | "contention" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "misconfigured" | "unknown";
|
|
1525
1531
|
declare class MessagingError extends Error {
|
|
1526
1532
|
readonly code: MessagingErrorCode;
|
|
1527
1533
|
/** The remedy. Nothing fails silently, and nothing fails without saying what to do. */
|
|
1528
1534
|
readonly remedy: string;
|
|
1529
1535
|
readonly cause?: unknown;
|
|
1530
1536
|
constructor(code: MessagingErrorCode, message: string, remedy: string, cause?: unknown);
|
|
1531
|
-
/** True when
|
|
1537
|
+
/** True when re-running the same call is the fix. */
|
|
1532
1538
|
get isRetryable(): boolean;
|
|
1533
1539
|
}
|
|
1534
1540
|
interface PostgrestErrorLike {
|
package/dist/react.js
CHANGED
|
@@ -106,14 +106,15 @@ var MessagingError = class extends Error {
|
|
|
106
106
|
this.remedy = remedy;
|
|
107
107
|
if (cause !== void 0) this.cause = cause;
|
|
108
108
|
}
|
|
109
|
-
/** True when
|
|
109
|
+
/** True when re-running the same call is the fix. */
|
|
110
110
|
get isRetryable() {
|
|
111
|
-
return this.code === "session-unavailable" || this.code === "transport";
|
|
111
|
+
return this.code === "session-unavailable" || this.code === "contention" || this.code === "transport";
|
|
112
112
|
}
|
|
113
113
|
};
|
|
114
114
|
var FORBIDDEN_CODES = /* @__PURE__ */ new Set(["42501", "PGRST301", "PGRST302"]);
|
|
115
115
|
var NOT_FOUND_CODES = /* @__PURE__ */ new Set(["PGRST116", "PGRST205"]);
|
|
116
116
|
var CONFLICT_CODES = /* @__PURE__ */ new Set(["23505"]);
|
|
117
|
+
var CONTENTION_CODES = /* @__PURE__ */ new Set(["40P01", "40001", "55P03"]);
|
|
117
118
|
var SESSION_MARKERS = [
|
|
118
119
|
"auth session missing",
|
|
119
120
|
"jwt expired",
|
|
@@ -135,6 +136,14 @@ function normalizeMessagingError(error, operation) {
|
|
|
135
136
|
error
|
|
136
137
|
);
|
|
137
138
|
}
|
|
139
|
+
if (code !== void 0 && CONTENTION_CODES.has(code)) {
|
|
140
|
+
return new MessagingError(
|
|
141
|
+
"contention",
|
|
142
|
+
`${operation}: the database resolved a lock collision by cancelling this call (${code}: ${message})`,
|
|
143
|
+
"Nothing of this call committed, so it is safe to re-run \u2014 the package re-runs it a bounded number of times and announces each heal. Seeing it repeatedly means real contention on the messaging tables (usually DDL applied against live traffic); look at what else held a lock at that moment, never at the caller.",
|
|
144
|
+
error
|
|
145
|
+
);
|
|
146
|
+
}
|
|
138
147
|
if (code !== void 0 && FORBIDDEN_CODES.has(code)) {
|
|
139
148
|
return new MessagingError(
|
|
140
149
|
"forbidden",
|
|
@@ -162,7 +171,7 @@ function normalizeMessagingError(error, operation) {
|
|
|
162
171
|
return new MessagingError(
|
|
163
172
|
"transport",
|
|
164
173
|
`${operation}: ${message}`,
|
|
165
|
-
"Transient transport failure.
|
|
174
|
+
"Transient transport failure. A typed message is surfaced to the outbox so it is never lost; a read is the caller's to re-issue (only `contention` and `session-unavailable` are re-run inside the package).",
|
|
166
175
|
error
|
|
167
176
|
);
|
|
168
177
|
}
|
|
@@ -1469,6 +1478,12 @@ function requireOrg(organizationId, operation) {
|
|
|
1469
1478
|
}
|
|
1470
1479
|
return organizationId;
|
|
1471
1480
|
}
|
|
1481
|
+
var CONTENTION_ATTEMPTS = 3;
|
|
1482
|
+
var CONTENTION_BACKOFF_MS = [60, 180];
|
|
1483
|
+
function sleep(ms) {
|
|
1484
|
+
const jittered = ms + Math.random() * ms * 0.5;
|
|
1485
|
+
return new Promise((resolve) => setTimeout(resolve, jittered));
|
|
1486
|
+
}
|
|
1472
1487
|
function createMessagingRepository(options) {
|
|
1473
1488
|
const { identity } = options;
|
|
1474
1489
|
const org = requireOrg(identity.organizationId, "createMessagingRepository");
|
|
@@ -1480,18 +1495,31 @@ function createMessagingRepository(options) {
|
|
|
1480
1495
|
const db = () => client.schema(MESSAGING_SCHEMA);
|
|
1481
1496
|
const rpcDb = () => client.schema(MESSAGING_RPC_SCHEMA);
|
|
1482
1497
|
async function withSessionRetry(operation, run) {
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
} catch (error) {
|
|
1486
|
-
const normalized = normalizeMessagingError(error, operation);
|
|
1487
|
-
if (normalized.code !== "session-unavailable" || options.resolveSession === void 0) {
|
|
1488
|
-
throw normalized;
|
|
1489
|
-
}
|
|
1490
|
-
await options.resolveSession();
|
|
1498
|
+
let sessionRetried = false;
|
|
1499
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
1491
1500
|
try {
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1501
|
+
const value = await run();
|
|
1502
|
+
if (attempt > 1) {
|
|
1503
|
+
options.onDiagnostic?.(
|
|
1504
|
+
`${operation}: healed on attempt ${attempt} after a database lock collision (rolled back, safe to re-run).`
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
return value;
|
|
1508
|
+
} catch (error) {
|
|
1509
|
+
const normalized = normalizeMessagingError(error, operation);
|
|
1510
|
+
if (normalized.code === "session-unavailable" && options.resolveSession !== void 0 && !sessionRetried) {
|
|
1511
|
+
sessionRetried = true;
|
|
1512
|
+
await options.resolveSession();
|
|
1513
|
+
continue;
|
|
1514
|
+
}
|
|
1515
|
+
if (normalized.code === "contention" && attempt < CONTENTION_ATTEMPTS) {
|
|
1516
|
+
options.onDiagnostic?.(
|
|
1517
|
+
`${operation}: attempt ${attempt}/${CONTENTION_ATTEMPTS} hit a database lock collision (${normalized.message}) \u2014 re-running in ${CONTENTION_BACKOFF_MS[attempt - 1] ?? 0}ms.`
|
|
1518
|
+
);
|
|
1519
|
+
await sleep(CONTENTION_BACKOFF_MS[attempt - 1] ?? 0);
|
|
1520
|
+
continue;
|
|
1521
|
+
}
|
|
1522
|
+
throw normalized;
|
|
1495
1523
|
}
|
|
1496
1524
|
}
|
|
1497
1525
|
}
|
|
@@ -1875,6 +1903,10 @@ function MessagingRuntime(props) {
|
|
|
1875
1903
|
// contract, which `supabase-shape.test.ts` proves a real client honors.
|
|
1876
1904
|
client,
|
|
1877
1905
|
identity,
|
|
1906
|
+
// An automatic heal ANNOUNCES itself: a lock collision the package
|
|
1907
|
+
// re-ran is a warning in the host's diagnostics, never a banner on the
|
|
1908
|
+
// inbox and never silence.
|
|
1909
|
+
onDiagnostic: (message) => report({ level: "warn", message }),
|
|
1878
1910
|
...props.resolveSession !== void 0 ? { resolveSession: props.resolveSession } : {}
|
|
1879
1911
|
});
|
|
1880
1912
|
let built = null;
|