@rebasepro/server 0.9.1-canary.d906fb7 → 0.9.1-canary.e3f810f
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/index.es.js
CHANGED
|
@@ -3305,7 +3305,7 @@ var BackendCollectionRegistry = class extends CollectionRegistry {
|
|
|
3305
3305
|
//#endregion
|
|
3306
3306
|
//#region src/collections/loader.ts
|
|
3307
3307
|
function isCollectionFile(file) {
|
|
3308
|
-
return (file.endsWith(".ts") || file.endsWith(".js")) && !file.includes(".test.") && !file.endsWith(".d.ts") && file !== "index.ts" && file !== "index.js";
|
|
3308
|
+
return (file.endsWith(".ts") || file.endsWith(".js")) && !file.startsWith(".") && !file.includes(".test.") && !file.endsWith(".d.ts") && file !== "index.ts" && file !== "index.js";
|
|
3309
3309
|
}
|
|
3310
3310
|
async function importModule(filePath) {
|
|
3311
3311
|
return await import(pathToFileURL(filePath).href);
|
|
@@ -3502,6 +3502,21 @@ function createRoutedRealtimeService(opts) {
|
|
|
3502
3502
|
//#region src/api/errors.ts
|
|
3503
3503
|
/** Tracks whether we've already shown the doctor hint (once per process). */
|
|
3504
3504
|
var _schemaDriftHinted = false;
|
|
3505
|
+
/** 5-character SQLSTATE, e.g. `42501`, `23505`. */
|
|
3506
|
+
var SQLSTATE_RE = /^[0-9A-Z]{5}$/;
|
|
3507
|
+
/**
|
|
3508
|
+
* Walk the cause chain for the underlying database error, identified by a
|
|
3509
|
+
* 5-char SQLSTATE `code`. Drizzle wraps the pg error in `.cause`, and route
|
|
3510
|
+
* code sometimes wraps drizzle again, so the real error may sit several
|
|
3511
|
+
* levels down.
|
|
3512
|
+
*/
|
|
3513
|
+
function extractDbError(error, depth = 0) {
|
|
3514
|
+
if (!error || typeof error !== "object" || depth > 8) return null;
|
|
3515
|
+
const e = error;
|
|
3516
|
+
if (typeof e.code === "string" && SQLSTATE_RE.test(e.code)) return e;
|
|
3517
|
+
if (e.cause && typeof e.cause === "object") return extractDbError(e.cause, depth + 1);
|
|
3518
|
+
return null;
|
|
3519
|
+
}
|
|
3505
3520
|
/**
|
|
3506
3521
|
* Extract the missing table or column name from a PG error.
|
|
3507
3522
|
* PG 42P01 messages look like: 'relation "my_table" does not exist'
|
|
@@ -3586,26 +3601,31 @@ var errorHandler = (err, c) => {
|
|
|
3586
3601
|
if (cause.code === "ECONNREFUSED" && !cause.address && Array.isArray(cause.errors)) resolvedCause = cause.errors.find((e) => e.address) || cause;
|
|
3587
3602
|
else resolvedCause = cause;
|
|
3588
3603
|
}
|
|
3589
|
-
|
|
3604
|
+
const dbError = extractDbError(error);
|
|
3605
|
+
if (resolvedCause && (resolvedCause.code === "ENETUNREACH" || resolvedCause.code === "ECONNREFUSED")) {
|
|
3590
3606
|
const cause = resolvedCause;
|
|
3591
3607
|
if (cause.code === "ENETUNREACH") logMessage = `Network unreachable. Cannot connect to database at ${cause.address}:${cause.port}.`;
|
|
3592
|
-
else
|
|
3593
|
-
else if (cause.code === "42703" || cause.code === "42P01") {
|
|
3594
|
-
code = "SCHEMA_DRIFT";
|
|
3595
|
-
logMessage = `Schema drift: ${cause.code === "42703" ? "column" : "table"} "${cause.table || cause.column || extractMissingIdentifier(cause.message) || "unknown"}" does not exist in the database. Run \`pnpm db:push\` to sync your schema, or \`pnpm db:migrate\` to apply pending migrations.`;
|
|
3596
|
-
}
|
|
3608
|
+
else logMessage = `Connection refused to database at ${cause.address}:${cause.port}. Is PostgreSQL running?`;
|
|
3597
3609
|
} else if ("code" in error && error.code === "ENETUNREACH") {
|
|
3598
3610
|
const netErr = error;
|
|
3599
3611
|
logMessage = `Network unreachable. Cannot connect to service at ${netErr.address}:${netErr.port}.`;
|
|
3600
|
-
} else if (
|
|
3612
|
+
} else if (dbError && (dbError.code === "42703" || dbError.code === "42P01")) {
|
|
3601
3613
|
code = "SCHEMA_DRIFT";
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3614
|
+
logMessage = `Schema drift: ${dbError.code === "42703" ? "column" : "table"} "${dbError.table || dbError.column || extractMissingIdentifier(dbError.message) || "unknown"}" does not exist in the database. Run \`pnpm db:push\` to sync your schema, or \`pnpm db:migrate\` to apply pending migrations.`;
|
|
3615
|
+
} else if (dbError) {
|
|
3616
|
+
const parts = [`[PG ${dbError.code}] ${dbError.message}`];
|
|
3617
|
+
if (dbError.detail) parts.push(`Detail: ${dbError.detail}`);
|
|
3618
|
+
if (dbError.hint) parts.push(`Hint: ${dbError.hint}`);
|
|
3619
|
+
if (dbError.table) parts.push(`Table: ${dbError.table}`);
|
|
3620
|
+
if (dbError.column) parts.push(`Column: ${dbError.column}`);
|
|
3621
|
+
if (dbError.constraint) parts.push(`Constraint: ${dbError.constraint}`);
|
|
3622
|
+
if (dbError.code === "42501") {
|
|
3623
|
+
code = "DB_PERMISSION_DENIED";
|
|
3624
|
+
parts.push(`The database rejected the statement for lack of privilege — usually a row-level security policy${dbError.table ? ` on "${dbError.table}"` : ""} denying this role, or a stale FORCE ROW LEVEL SECURITY flag binding the owner connection.`);
|
|
3625
|
+
}
|
|
3626
|
+
logMessage = parts.join(". ");
|
|
3627
|
+
}
|
|
3628
|
+
const isDbSchemaMismatch = code === "SCHEMA_DRIFT";
|
|
3609
3629
|
if (isDbSchemaMismatch) {
|
|
3610
3630
|
logger.warn(`⚠️ [API] ${c.req.method} ${c.req.path} → ${statusCode} ${code}: ${logMessage}` + (reqId ? ` [${reqId}]` : ""));
|
|
3611
3631
|
if (!_schemaDriftHinted && process.env.NODE_ENV !== "production") {
|
|
@@ -3624,18 +3644,27 @@ var errorHandler = (err, c) => {
|
|
|
3624
3644
|
].join("\n"));
|
|
3625
3645
|
}
|
|
3626
3646
|
} else logger.error(`❌ [API] ${c.req.method} ${c.req.path} → ${statusCode} ${code}: ${logMessage}` + (reqId ? ` [${reqId}]` : ""));
|
|
3627
|
-
if (!(isDbSchemaMismatch || statusCode < 500 && code === "BAD_REQUEST")) logger.error(String(error.stack || error));
|
|
3647
|
+
if (!(isDbSchemaMismatch || dbError !== null || statusCode < 500 && code === "BAD_REQUEST")) logger.error(String(error.stack || error));
|
|
3628
3648
|
let clientMessage = "An unexpected error occurred";
|
|
3629
3649
|
if (statusCode < 500 && error.message) clientMessage = error.message;
|
|
3630
3650
|
else if (error instanceof ApiError || error.name === "ApiError") clientMessage = error.message;
|
|
3631
3651
|
else if (code === "SCHEMA_DRIFT") {
|
|
3632
|
-
const pgErr =
|
|
3633
|
-
clientMessage = `Schema drift: ${pgErr.code === "42703"
|
|
3634
|
-
} else if (code === "
|
|
3652
|
+
const pgErr = dbError || error;
|
|
3653
|
+
clientMessage = `Schema drift: ${pgErr.code === "42703" ? "column" : "table"} "${pgErr.table || pgErr.column || extractMissingIdentifier(pgErr.message || error.message) || "unknown"}" does not exist. Run \`pnpm db:push\` to sync your schema.`;
|
|
3654
|
+
} else if (code === "DB_PERMISSION_DENIED") clientMessage = `Permission denied by the database${dbError?.table ? ` on "${dbError.table}"` : ""} (row-level security). Check the RLS policies for this table.`;
|
|
3655
|
+
else if (code === "INTERNAL_ERROR") clientMessage = "Internal Server Error";
|
|
3656
|
+
const dbDetails = dbError ? {
|
|
3657
|
+
dbCode: dbError.code,
|
|
3658
|
+
...process.env.NODE_ENV !== "production" && {
|
|
3659
|
+
dbMessage: dbError.message,
|
|
3660
|
+
...dbError.detail && { detail: dbError.detail },
|
|
3661
|
+
...dbError.hint && { hint: dbError.hint }
|
|
3662
|
+
}
|
|
3663
|
+
} : void 0;
|
|
3635
3664
|
return c.json({ error: {
|
|
3636
3665
|
message: clientMessage,
|
|
3637
3666
|
code,
|
|
3638
|
-
...error.details !== void 0
|
|
3667
|
+
...error.details !== void 0 ? { details: error.details } : dbDetails !== void 0 ? { details: dbDetails } : {},
|
|
3639
3668
|
...reqId && { requestId: reqId }
|
|
3640
3669
|
} }, statusCode);
|
|
3641
3670
|
};
|
|
@@ -3657,6 +3686,7 @@ function codeToStatus(code) {
|
|
|
3657
3686
|
EMAIL_EXISTS: 409,
|
|
3658
3687
|
ROLE_EXISTS: 409,
|
|
3659
3688
|
SCHEMA_DRIFT: 500,
|
|
3689
|
+
DB_PERMISSION_DENIED: 500,
|
|
3660
3690
|
INTERNAL_ERROR: 500,
|
|
3661
3691
|
NOT_CONFIGURED: 503,
|
|
3662
3692
|
SERVICE_UNAVAILABLE: 503
|
|
@@ -14382,7 +14412,7 @@ async function initializeStorage(storageConfig, isProduction) {
|
|
|
14382
14412
|
//#region src/init/docs.ts
|
|
14383
14413
|
async function mountOpenApiDocs(app, basePath, enableSwagger, activeCollections, requireAuth) {
|
|
14384
14414
|
if (enableSwagger === false || activeCollections.length === 0) return;
|
|
14385
|
-
const { generateOpenApiSpec } = await import("./openapi-generator-
|
|
14415
|
+
const { generateOpenApiSpec } = await import("./openapi-generator-DqSIwNLV.js");
|
|
14386
14416
|
app.get(`${basePath}/docs`, (c) => {
|
|
14387
14417
|
const spec = generateOpenApiSpec(activeCollections, {
|
|
14388
14418
|
basePath,
|
|
@@ -15903,9 +15933,11 @@ var ClientStorageSourceRegistry = class ClientStorageSourceRegistry {
|
|
|
15903
15933
|
function extractMessageError(message) {
|
|
15904
15934
|
const payload = message.payload;
|
|
15905
15935
|
const errPayload = payload?.error;
|
|
15936
|
+
const errorMessage = typeof errPayload === "object" ? errPayload.message : payload?.message || (typeof errPayload === "string" ? errPayload : void 0) || message.error || "Unknown error";
|
|
15937
|
+
const errorCode = typeof errPayload === "object" ? errPayload.code : payload?.code;
|
|
15906
15938
|
return {
|
|
15907
|
-
errorMessage: typeof
|
|
15908
|
-
errorCode
|
|
15939
|
+
errorMessage: typeof errorMessage === "string" ? errorMessage : errorMessage == null ? "Unknown error" : JSON.stringify(errorMessage),
|
|
15940
|
+
errorCode
|
|
15909
15941
|
};
|
|
15910
15942
|
}
|
|
15911
15943
|
/**
|
|
@@ -15941,6 +15973,7 @@ var RebaseWebSocketClient = class {
|
|
|
15941
15973
|
isConnected = false;
|
|
15942
15974
|
messageQueue = [];
|
|
15943
15975
|
requestTimeoutMs = 3e4;
|
|
15976
|
+
subscriptionTimeoutMs = 3e4;
|
|
15944
15977
|
reconnectTimeout = null;
|
|
15945
15978
|
isAuthenticated = false;
|
|
15946
15979
|
authPromise = null;
|
|
@@ -16046,6 +16079,7 @@ var RebaseWebSocketClient = class {
|
|
|
16046
16079
|
this.emit(wasReconnect ? "reconnect" : "connect");
|
|
16047
16080
|
this.processMessageQueue();
|
|
16048
16081
|
if (wasReconnect) this.resubscribeAll();
|
|
16082
|
+
this.armPendingSubscribeWatchdogs();
|
|
16049
16083
|
};
|
|
16050
16084
|
this.ws.onmessage = (event) => {
|
|
16051
16085
|
try {
|
|
@@ -16060,6 +16094,7 @@ var RebaseWebSocketClient = class {
|
|
|
16060
16094
|
this.isConnected = false;
|
|
16061
16095
|
this.isAuthenticated = false;
|
|
16062
16096
|
this.authPromise = null;
|
|
16097
|
+
this.suspendSubscribeWatchdogs();
|
|
16063
16098
|
this.emit("disconnect");
|
|
16064
16099
|
for (const [reqId, request] of this.pendingRequests.entries()) {
|
|
16065
16100
|
if (reqId.startsWith("auth_")) request.reject(/* @__PURE__ */ new Error("Connection closed during authentication"));
|
|
@@ -16091,6 +16126,7 @@ var RebaseWebSocketClient = class {
|
|
|
16091
16126
|
attemptReconnect() {
|
|
16092
16127
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
16093
16128
|
console.error("Max reconnection attempts reached");
|
|
16129
|
+
this.failAllPendingSubscriptions(new RebaseApiError("Connection lost", { code: "CONNECTION_LOST" }));
|
|
16094
16130
|
return;
|
|
16095
16131
|
}
|
|
16096
16132
|
this.reconnectAttempts++;
|
|
@@ -16145,29 +16181,18 @@ var RebaseWebSocketClient = class {
|
|
|
16145
16181
|
subscription.backendSubscriptionId = newBackendId;
|
|
16146
16182
|
backendKeyMap.delete(oldBackendId);
|
|
16147
16183
|
backendKeyMap.set(newBackendId, subscriptionKey);
|
|
16148
|
-
this.
|
|
16149
|
-
|
|
16150
|
-
|
|
16151
|
-
...subscription.props,
|
|
16152
|
-
subscriptionId: newBackendId
|
|
16153
|
-
}
|
|
16154
|
-
}).catch((error) => {
|
|
16155
|
-
console.error(`[WS] Failed to re-subscribe ${idPrefix} after auth refresh:`, subscriptionKey, error);
|
|
16156
|
-
subscription.callbacks.forEach((callback) => {
|
|
16157
|
-
if (callback.onError) callback.onError(error);
|
|
16158
|
-
});
|
|
16159
|
-
});
|
|
16160
|
-
} else {
|
|
16161
|
-
const { errorMessage, errorCode } = extractMessageError(message);
|
|
16162
|
-
const error = new RebaseApiError(errorMessage, { code: errorCode });
|
|
16163
|
-
subscription.callbacks.forEach((callback) => {
|
|
16164
|
-
if (callback.onError) callback.onError(error);
|
|
16165
|
-
});
|
|
16184
|
+
if (messageType === "subscribe_collection") this.sendCollectionSubscribe(subscriptionKey);
|
|
16185
|
+
else this.sendEntitySubscribe(subscriptionKey);
|
|
16186
|
+
return;
|
|
16166
16187
|
}
|
|
16188
|
+
const { errorMessage, errorCode } = extractMessageError(message);
|
|
16189
|
+
const error = new RebaseApiError(errorMessage, { code: errorCode });
|
|
16190
|
+
if (messageType === "subscribe_collection") this.failCollectionSubscription(subscriptionKey, error);
|
|
16191
|
+
else this.failEntitySubscription(subscriptionKey, error);
|
|
16167
16192
|
}).catch((err) => {
|
|
16168
|
-
|
|
16169
|
-
|
|
16170
|
-
|
|
16193
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
16194
|
+
if (messageType === "subscribe_collection") this.failCollectionSubscription(subscriptionKey, error);
|
|
16195
|
+
else this.failEntitySubscription(subscriptionKey, error);
|
|
16171
16196
|
});
|
|
16172
16197
|
}
|
|
16173
16198
|
handleWebSocketMessage(message) {
|
|
@@ -16208,6 +16233,9 @@ var RebaseWebSocketClient = class {
|
|
|
16208
16233
|
collectionSub.latestData = rows;
|
|
16209
16234
|
collectionSub.lastUpdated = Date.now();
|
|
16210
16235
|
collectionSub.isInitialDataReceived = true;
|
|
16236
|
+
if (collectionSub.subscribeTimeout) clearTimeout(collectionSub.subscribeTimeout);
|
|
16237
|
+
collectionSub.subscribeTimeout = void 0;
|
|
16238
|
+
collectionSub.subscribeInFlight = false;
|
|
16211
16239
|
collectionSub.callbacks.forEach((callback) => {
|
|
16212
16240
|
try {
|
|
16213
16241
|
callback.onUpdate(rows);
|
|
@@ -16263,6 +16291,9 @@ var RebaseWebSocketClient = class {
|
|
|
16263
16291
|
entitySub.latestData = row;
|
|
16264
16292
|
entitySub.lastUpdated = Date.now();
|
|
16265
16293
|
entitySub.isInitialDataReceived = true;
|
|
16294
|
+
if (entitySub.subscribeTimeout) clearTimeout(entitySub.subscribeTimeout);
|
|
16295
|
+
entitySub.subscribeTimeout = void 0;
|
|
16296
|
+
entitySub.subscribeInFlight = false;
|
|
16266
16297
|
entitySub.callbacks.forEach((callback) => {
|
|
16267
16298
|
try {
|
|
16268
16299
|
callback.onUpdate(row);
|
|
@@ -16284,6 +16315,9 @@ var RebaseWebSocketClient = class {
|
|
|
16284
16315
|
this.resubscribeAfterAuthRefresh(message, collectionSub, collectionKey, "collection", this.backendToCollectionKey, "subscribe_collection");
|
|
16285
16316
|
return;
|
|
16286
16317
|
}
|
|
16318
|
+
if (collectionSub.subscribeTimeout) clearTimeout(collectionSub.subscribeTimeout);
|
|
16319
|
+
collectionSub.subscribeTimeout = void 0;
|
|
16320
|
+
collectionSub.subscribeInFlight = false;
|
|
16287
16321
|
const { errorMessage, errorCode } = extractMessageError(message);
|
|
16288
16322
|
const error = new RebaseApiError(errorMessage, { code: errorCode });
|
|
16289
16323
|
collectionSub.callbacks.forEach((callback) => {
|
|
@@ -16300,6 +16334,9 @@ var RebaseWebSocketClient = class {
|
|
|
16300
16334
|
this.resubscribeAfterAuthRefresh(message, entitySub, entityKey, "row", this.backendToEntityKey, "subscribe_one");
|
|
16301
16335
|
return;
|
|
16302
16336
|
}
|
|
16337
|
+
if (entitySub.subscribeTimeout) clearTimeout(entitySub.subscribeTimeout);
|
|
16338
|
+
entitySub.subscribeTimeout = void 0;
|
|
16339
|
+
entitySub.subscribeInFlight = false;
|
|
16303
16340
|
const { errorMessage, errorCode } = extractMessageError(message);
|
|
16304
16341
|
const error = new RebaseApiError(errorMessage, { code: errorCode });
|
|
16305
16342
|
entitySub.callbacks.forEach((callback) => {
|
|
@@ -16648,9 +16685,12 @@ var RebaseWebSocketClient = class {
|
|
|
16648
16685
|
console.error("Error in collection subscription callback:", error);
|
|
16649
16686
|
if (onError) onError(error instanceof Error ? error : new Error(String(error)));
|
|
16650
16687
|
}
|
|
16688
|
+
else if (!existingSubscription.subscribeInFlight) this.sendCollectionSubscribe(subscriptionKey);
|
|
16651
16689
|
return () => {
|
|
16652
16690
|
callbackMap.delete(callbackId);
|
|
16653
16691
|
if (callbackMap.size === 0) {
|
|
16692
|
+
if (this.collectionSubscriptions.get(subscriptionKey) !== existingSubscription) return;
|
|
16693
|
+
if (existingSubscription.subscribeTimeout) clearTimeout(existingSubscription.subscribeTimeout);
|
|
16654
16694
|
this.collectionSubscriptions.delete(subscriptionKey);
|
|
16655
16695
|
this.backendToCollectionKey.delete(existingSubscription.backendSubscriptionId);
|
|
16656
16696
|
if (this.isConnected && this.ws) this.sendMessage({
|
|
@@ -16672,21 +16712,14 @@ var RebaseWebSocketClient = class {
|
|
|
16672
16712
|
props
|
|
16673
16713
|
});
|
|
16674
16714
|
this.backendToCollectionKey.set(backendSubscriptionId, subscriptionKey);
|
|
16675
|
-
this.
|
|
16676
|
-
type: "subscribe_collection",
|
|
16677
|
-
payload: {
|
|
16678
|
-
...props,
|
|
16679
|
-
subscriptionId: backendSubscriptionId
|
|
16680
|
-
}
|
|
16681
|
-
}).catch((error) => {
|
|
16682
|
-
if (onError) onError(error);
|
|
16683
|
-
});
|
|
16715
|
+
this.sendCollectionSubscribe(subscriptionKey);
|
|
16684
16716
|
return () => {
|
|
16685
16717
|
const subscription = this.collectionSubscriptions.get(subscriptionKey);
|
|
16686
16718
|
if (subscription) {
|
|
16687
16719
|
const callbacks = subscription.callbacks;
|
|
16688
16720
|
callbacks.delete(callbackId);
|
|
16689
16721
|
if (callbacks.size === 0) {
|
|
16722
|
+
if (subscription.subscribeTimeout) clearTimeout(subscription.subscribeTimeout);
|
|
16690
16723
|
this.collectionSubscriptions.delete(subscriptionKey);
|
|
16691
16724
|
this.backendToCollectionKey.delete(subscription.backendSubscriptionId);
|
|
16692
16725
|
if (this.isConnected && this.ws) this.sendMessage({
|
|
@@ -16713,9 +16746,12 @@ var RebaseWebSocketClient = class {
|
|
|
16713
16746
|
console.error("Error in row subscription callback:", error);
|
|
16714
16747
|
if (onError) onError(error instanceof Error ? error : new Error(String(error)));
|
|
16715
16748
|
}
|
|
16749
|
+
else if (!existingSubscription.subscribeInFlight) this.sendEntitySubscribe(subscriptionKey);
|
|
16716
16750
|
return () => {
|
|
16717
16751
|
callbackMap.delete(callbackId);
|
|
16718
16752
|
if (callbackMap.size === 0) {
|
|
16753
|
+
if (this.singleSubscriptions.get(subscriptionKey) !== existingSubscription) return;
|
|
16754
|
+
if (existingSubscription.subscribeTimeout) clearTimeout(existingSubscription.subscribeTimeout);
|
|
16719
16755
|
this.singleSubscriptions.delete(subscriptionKey);
|
|
16720
16756
|
this.backendToEntityKey.delete(existingSubscription.backendSubscriptionId);
|
|
16721
16757
|
if (this.isConnected && this.ws) this.sendMessage({
|
|
@@ -16737,15 +16773,7 @@ var RebaseWebSocketClient = class {
|
|
|
16737
16773
|
props
|
|
16738
16774
|
});
|
|
16739
16775
|
this.backendToEntityKey.set(backendSubscriptionId, subscriptionKey);
|
|
16740
|
-
this.
|
|
16741
|
-
type: "subscribe_one",
|
|
16742
|
-
payload: {
|
|
16743
|
-
...props,
|
|
16744
|
-
subscriptionId: backendSubscriptionId
|
|
16745
|
-
}
|
|
16746
|
-
}).catch((error) => {
|
|
16747
|
-
if (onError) onError(error);
|
|
16748
|
-
});
|
|
16776
|
+
this.sendEntitySubscribe(subscriptionKey);
|
|
16749
16777
|
return () => {
|
|
16750
16778
|
const subscription = this.singleSubscriptions.get(subscriptionKey);
|
|
16751
16779
|
if (subscription) {
|
|
@@ -16763,6 +16791,157 @@ var RebaseWebSocketClient = class {
|
|
|
16763
16791
|
};
|
|
16764
16792
|
}
|
|
16765
16793
|
/**
|
|
16794
|
+
* Send a `subscribe_collection` for an already-registered subscription and
|
|
16795
|
+
* arm its watchdog.
|
|
16796
|
+
*
|
|
16797
|
+
* Every path that registers a collection subscription goes through here, so
|
|
16798
|
+
* that a subscribe which never lands — a rejected send, or a server that
|
|
16799
|
+
* never answers — always ends up in `failCollectionSubscription` rather than
|
|
16800
|
+
* leaving the entry parked with `isInitialDataReceived === false` forever.
|
|
16801
|
+
*/
|
|
16802
|
+
sendCollectionSubscribe(subscriptionKey) {
|
|
16803
|
+
const subscription = this.collectionSubscriptions.get(subscriptionKey);
|
|
16804
|
+
if (!subscription) return;
|
|
16805
|
+
const backendSubscriptionId = subscription.backendSubscriptionId;
|
|
16806
|
+
subscription.subscribeInFlight = true;
|
|
16807
|
+
if (subscription.subscribeTimeout) clearTimeout(subscription.subscribeTimeout);
|
|
16808
|
+
subscription.subscribeTimeout = void 0;
|
|
16809
|
+
if (this.isConnected) this.sendCollectionSubscribeWatchdog(subscriptionKey);
|
|
16810
|
+
this.sendMessage({
|
|
16811
|
+
type: "subscribe_collection",
|
|
16812
|
+
payload: {
|
|
16813
|
+
...subscription.props,
|
|
16814
|
+
subscriptionId: backendSubscriptionId
|
|
16815
|
+
}
|
|
16816
|
+
}).catch((error) => {
|
|
16817
|
+
const current = this.collectionSubscriptions.get(subscriptionKey);
|
|
16818
|
+
if (!current || current.backendSubscriptionId !== backendSubscriptionId) return;
|
|
16819
|
+
this.failCollectionSubscription(subscriptionKey, error instanceof Error ? error : new Error(String(error)));
|
|
16820
|
+
});
|
|
16821
|
+
}
|
|
16822
|
+
/** The `listenOne` counterpart of {@link sendCollectionSubscribe}. */
|
|
16823
|
+
sendEntitySubscribe(subscriptionKey) {
|
|
16824
|
+
const subscription = this.singleSubscriptions.get(subscriptionKey);
|
|
16825
|
+
if (!subscription) return;
|
|
16826
|
+
const backendSubscriptionId = subscription.backendSubscriptionId;
|
|
16827
|
+
subscription.subscribeInFlight = true;
|
|
16828
|
+
if (subscription.subscribeTimeout) clearTimeout(subscription.subscribeTimeout);
|
|
16829
|
+
subscription.subscribeTimeout = void 0;
|
|
16830
|
+
if (this.isConnected) this.sendEntitySubscribeWatchdog(subscriptionKey);
|
|
16831
|
+
this.sendMessage({
|
|
16832
|
+
type: "subscribe_one",
|
|
16833
|
+
payload: {
|
|
16834
|
+
...subscription.props,
|
|
16835
|
+
subscriptionId: backendSubscriptionId
|
|
16836
|
+
}
|
|
16837
|
+
}).catch((error) => {
|
|
16838
|
+
const current = this.singleSubscriptions.get(subscriptionKey);
|
|
16839
|
+
if (!current || current.backendSubscriptionId !== backendSubscriptionId) return;
|
|
16840
|
+
this.failEntitySubscription(subscriptionKey, error instanceof Error ? error : new Error(String(error)));
|
|
16841
|
+
});
|
|
16842
|
+
}
|
|
16843
|
+
/**
|
|
16844
|
+
* Report a subscribe failure to every listener and drop the registration.
|
|
16845
|
+
*
|
|
16846
|
+
* Dropping it is the point: the callbacks stay live (their components are
|
|
16847
|
+
* still mounted and have been told), but the next `listenCollection` for
|
|
16848
|
+
* these params finds no entry and issues a fresh subscribe instead of
|
|
16849
|
+
* silently attaching to a dead one.
|
|
16850
|
+
*/
|
|
16851
|
+
failCollectionSubscription(subscriptionKey, error) {
|
|
16852
|
+
const subscription = this.collectionSubscriptions.get(subscriptionKey);
|
|
16853
|
+
if (!subscription) return;
|
|
16854
|
+
if (subscription.subscribeTimeout) clearTimeout(subscription.subscribeTimeout);
|
|
16855
|
+
subscription.subscribeInFlight = false;
|
|
16856
|
+
this.collectionSubscriptions.delete(subscriptionKey);
|
|
16857
|
+
this.backendToCollectionKey.delete(subscription.backendSubscriptionId);
|
|
16858
|
+
subscription.callbacks.forEach((callback) => {
|
|
16859
|
+
if (callback.onError) try {
|
|
16860
|
+
callback.onError(error);
|
|
16861
|
+
} catch (callbackError) {
|
|
16862
|
+
console.error("Error in collection subscription error callback:", callbackError);
|
|
16863
|
+
}
|
|
16864
|
+
});
|
|
16865
|
+
}
|
|
16866
|
+
/** The `listenOne` counterpart of {@link failCollectionSubscription}. */
|
|
16867
|
+
failEntitySubscription(subscriptionKey, error) {
|
|
16868
|
+
const subscription = this.singleSubscriptions.get(subscriptionKey);
|
|
16869
|
+
if (!subscription) return;
|
|
16870
|
+
if (subscription.subscribeTimeout) clearTimeout(subscription.subscribeTimeout);
|
|
16871
|
+
subscription.subscribeInFlight = false;
|
|
16872
|
+
this.singleSubscriptions.delete(subscriptionKey);
|
|
16873
|
+
this.backendToEntityKey.delete(subscription.backendSubscriptionId);
|
|
16874
|
+
subscription.callbacks.forEach((callback) => {
|
|
16875
|
+
if (callback.onError) try {
|
|
16876
|
+
callback.onError(error);
|
|
16877
|
+
} catch (callbackError) {
|
|
16878
|
+
console.error("Error in row subscription error callback:", callbackError);
|
|
16879
|
+
}
|
|
16880
|
+
});
|
|
16881
|
+
}
|
|
16882
|
+
/**
|
|
16883
|
+
* Stop the watchdogs without failing anything — used when the socket drops,
|
|
16884
|
+
* since the reconnect path re-subscribes everything anyway and a watchdog
|
|
16885
|
+
* firing mid-reconnect would tear down healthy subscriptions.
|
|
16886
|
+
*/
|
|
16887
|
+
suspendSubscribeWatchdogs() {
|
|
16888
|
+
for (const sub of this.collectionSubscriptions.values()) {
|
|
16889
|
+
if (sub.subscribeTimeout) clearTimeout(sub.subscribeTimeout);
|
|
16890
|
+
sub.subscribeTimeout = void 0;
|
|
16891
|
+
sub.subscribeInFlight = false;
|
|
16892
|
+
}
|
|
16893
|
+
for (const sub of this.singleSubscriptions.values()) {
|
|
16894
|
+
if (sub.subscribeTimeout) clearTimeout(sub.subscribeTimeout);
|
|
16895
|
+
sub.subscribeTimeout = void 0;
|
|
16896
|
+
sub.subscribeInFlight = false;
|
|
16897
|
+
}
|
|
16898
|
+
}
|
|
16899
|
+
/**
|
|
16900
|
+
* Arm watchdogs for subscribes that were requested while offline and have
|
|
16901
|
+
* just been flushed to the socket. Their timers were deliberately not set at
|
|
16902
|
+
* request time, so without this they would have no timeout at all.
|
|
16903
|
+
*/
|
|
16904
|
+
armPendingSubscribeWatchdogs() {
|
|
16905
|
+
for (const [key, sub] of this.collectionSubscriptions.entries()) if (sub.subscribeInFlight && !sub.subscribeTimeout) this.sendCollectionSubscribeWatchdog(key);
|
|
16906
|
+
for (const [key, sub] of this.singleSubscriptions.entries()) if (sub.subscribeInFlight && !sub.subscribeTimeout) this.sendEntitySubscribeWatchdog(key);
|
|
16907
|
+
}
|
|
16908
|
+
sendCollectionSubscribeWatchdog(subscriptionKey) {
|
|
16909
|
+
const subscription = this.collectionSubscriptions.get(subscriptionKey);
|
|
16910
|
+
if (!subscription) return;
|
|
16911
|
+
const backendSubscriptionId = subscription.backendSubscriptionId;
|
|
16912
|
+
subscription.subscribeTimeout = setTimeout(() => {
|
|
16913
|
+
const current = this.collectionSubscriptions.get(subscriptionKey);
|
|
16914
|
+
if (!current || current.backendSubscriptionId !== backendSubscriptionId) return;
|
|
16915
|
+
if (!current.subscribeInFlight) return;
|
|
16916
|
+
this.failCollectionSubscription(subscriptionKey, new RebaseApiError("Subscription timed out", { code: "SUBSCRIPTION_TIMEOUT" }));
|
|
16917
|
+
}, this.subscriptionTimeoutMs);
|
|
16918
|
+
}
|
|
16919
|
+
sendEntitySubscribeWatchdog(subscriptionKey) {
|
|
16920
|
+
const subscription = this.singleSubscriptions.get(subscriptionKey);
|
|
16921
|
+
if (!subscription) return;
|
|
16922
|
+
const backendSubscriptionId = subscription.backendSubscriptionId;
|
|
16923
|
+
subscription.subscribeTimeout = setTimeout(() => {
|
|
16924
|
+
const current = this.singleSubscriptions.get(subscriptionKey);
|
|
16925
|
+
if (!current || current.backendSubscriptionId !== backendSubscriptionId) return;
|
|
16926
|
+
if (!current.subscribeInFlight) return;
|
|
16927
|
+
this.failEntitySubscription(subscriptionKey, new RebaseApiError("Subscription timed out", { code: "SUBSCRIPTION_TIMEOUT" }));
|
|
16928
|
+
}, this.subscriptionTimeoutMs);
|
|
16929
|
+
}
|
|
16930
|
+
/**
|
|
16931
|
+
* Fail every subscription that never received data. Called when reconnection
|
|
16932
|
+
* is given up on, so views surface an error instead of spinning forever.
|
|
16933
|
+
*/
|
|
16934
|
+
failAllPendingSubscriptions(error) {
|
|
16935
|
+
for (const key of [...this.collectionSubscriptions.keys()]) {
|
|
16936
|
+
const sub = this.collectionSubscriptions.get(key);
|
|
16937
|
+
if (sub && !sub.isInitialDataReceived) this.failCollectionSubscription(key, error);
|
|
16938
|
+
}
|
|
16939
|
+
for (const key of [...this.singleSubscriptions.keys()]) {
|
|
16940
|
+
const sub = this.singleSubscriptions.get(key);
|
|
16941
|
+
if (sub && !sub.isInitialDataReceived) this.failEntitySubscription(key, error);
|
|
16942
|
+
}
|
|
16943
|
+
}
|
|
16944
|
+
/**
|
|
16766
16945
|
* Re-send all active subscriptions to the backend after a reconnect.
|
|
16767
16946
|
* The server wipes subscription state when a client disconnects, so
|
|
16768
16947
|
* we need to re-register everything to resume receiving updates.
|
|
@@ -16775,15 +16954,7 @@ var RebaseWebSocketClient = class {
|
|
|
16775
16954
|
sub.backendSubscriptionId = newBackendId;
|
|
16776
16955
|
this.backendToCollectionKey.delete(oldBackendId);
|
|
16777
16956
|
this.backendToCollectionKey.set(newBackendId, key);
|
|
16778
|
-
this.
|
|
16779
|
-
type: "subscribe_collection",
|
|
16780
|
-
payload: {
|
|
16781
|
-
...sub.props,
|
|
16782
|
-
subscriptionId: newBackendId
|
|
16783
|
-
}
|
|
16784
|
-
}).catch((error) => {
|
|
16785
|
-
console.error("[WS] Failed to re-subscribe collection:", key, error);
|
|
16786
|
-
});
|
|
16957
|
+
this.sendCollectionSubscribe(key);
|
|
16787
16958
|
}
|
|
16788
16959
|
for (const [key, sub] of this.singleSubscriptions.entries()) {
|
|
16789
16960
|
const oldBackendId = sub.backendSubscriptionId;
|
|
@@ -16791,15 +16962,7 @@ var RebaseWebSocketClient = class {
|
|
|
16791
16962
|
sub.backendSubscriptionId = newBackendId;
|
|
16792
16963
|
this.backendToEntityKey.delete(oldBackendId);
|
|
16793
16964
|
this.backendToEntityKey.set(newBackendId, key);
|
|
16794
|
-
this.
|
|
16795
|
-
type: "subscribe_one",
|
|
16796
|
-
payload: {
|
|
16797
|
-
...sub.props,
|
|
16798
|
-
subscriptionId: newBackendId
|
|
16799
|
-
}
|
|
16800
|
-
}).catch((error) => {
|
|
16801
|
-
console.error("[WS] Failed to re-subscribe row:", key, error);
|
|
16802
|
-
});
|
|
16965
|
+
this.sendEntitySubscribe(key);
|
|
16803
16966
|
}
|
|
16804
16967
|
}
|
|
16805
16968
|
createCollectionSubscriptionKey(props) {
|
|
@@ -17902,7 +18065,7 @@ async function loadFunctionsFromDirectory(directory, importModule = nativeDynami
|
|
|
17902
18065
|
const problems = [];
|
|
17903
18066
|
if (!fs$2.existsSync(directory)) return functions;
|
|
17904
18067
|
const files = fs$2.readdirSync(directory);
|
|
17905
|
-
for (const file of files) if ((file.endsWith(".ts") || file.endsWith(".js")) && !file.includes(".test.") && !file.endsWith(".d.ts") && file !== "index.ts" && file !== "index.js") {
|
|
18068
|
+
for (const file of files) if ((file.endsWith(".ts") || file.endsWith(".js")) && !file.startsWith(".") && !file.includes(".test.") && !file.endsWith(".d.ts") && file !== "index.ts" && file !== "index.js") {
|
|
17906
18069
|
const filePath = path$1.join(directory, file);
|
|
17907
18070
|
try {
|
|
17908
18071
|
const fileUrl = pathToFileURL(filePath).href;
|
|
@@ -18031,7 +18194,7 @@ async function loadCronJobsFromDirectory(directory, importModule = nativeDynamic
|
|
|
18031
18194
|
const jobs = [];
|
|
18032
18195
|
if (!fs$2.existsSync(directory)) return jobs;
|
|
18033
18196
|
const files = fs$2.readdirSync(directory);
|
|
18034
|
-
for (const file of files) if ((file.endsWith(".ts") || file.endsWith(".js")) && !file.includes(".test.") && !file.endsWith(".d.ts") && file !== "index.ts" && file !== "index.js") {
|
|
18197
|
+
for (const file of files) if ((file.endsWith(".ts") || file.endsWith(".js")) && !file.startsWith(".") && !file.includes(".test.") && !file.endsWith(".d.ts") && file !== "index.ts" && file !== "index.js") {
|
|
18035
18198
|
const filePath = path$1.join(directory, file);
|
|
18036
18199
|
try {
|
|
18037
18200
|
const fileUrl = pathToFileURL(filePath).href;
|