@rebasepro/client 0.9.1-canary.1d2d8b5 → 0.9.1-canary.58368ce
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.d.ts +9 -0
- package/dist/index.es.js +58 -11
- package/dist/index.es.js.map +1 -1
- package/dist/transport.d.ts +13 -0
- package/dist/websocket.d.ts +13 -0
- package/package.json +4 -4
- package/src/collection.ts +16 -0
- package/src/index.ts +26 -1
- package/src/realtime-optout.test.ts +119 -0
- package/src/realtime-row-identity.test.ts +254 -0
- package/src/sdk_query_builder.ts +4 -1
- package/src/transport.ts +13 -0
- package/src/websocket.ts +62 -11
- package/dist/collection.test.d.ts +0 -1
- package/dist/cron.test.d.ts +0 -1
- package/dist/data-proxy.test.d.ts +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -74,11 +74,20 @@ export type CreateRebaseClientResult<DB = Record<string, unknown>> = Omit<Rebase
|
|
|
74
74
|
apiKeys: ReturnType<typeof createApiKeys>;
|
|
75
75
|
functions: ReturnType<typeof createFunctionsClient>;
|
|
76
76
|
ws?: RebaseWebSocketClient;
|
|
77
|
+
/**
|
|
78
|
+
* Release the realtime socket and its reconnect timer.
|
|
79
|
+
*
|
|
80
|
+
* An open socket keeps the Node event loop alive, so a script that does not
|
|
81
|
+
* call this will not exit on its own. Safe when realtime was never started
|
|
82
|
+
* (`realtime: false`), and safe to call twice.
|
|
83
|
+
*/
|
|
84
|
+
close: () => void;
|
|
77
85
|
storage: StorageSource;
|
|
78
86
|
storageRegistry: StorageSourceRegistry;
|
|
79
87
|
createStorageSource: (storageId: string) => StorageSource;
|
|
80
88
|
fetchStorageSources: () => Promise<StorageSourceDefinition[]>;
|
|
81
89
|
call: <T = unknown>(endpoint: string, payload?: unknown) => Promise<T>;
|
|
90
|
+
collection: <M extends Record<string, unknown> = Record<string, unknown>>(slug: string) => CollectionClient<M>;
|
|
82
91
|
data: TypedDataLayer<DB>;
|
|
83
92
|
};
|
|
84
93
|
export declare function createRebaseClient<DB = Record<string, unknown>>(options: CreateRebaseClientOptions): CreateRebaseClientResult<DB>;
|
package/dist/index.es.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DEFAULT_STORAGE_SOURCE_KEY, EntityReference, EntityRelation, GeoPoint, PUBLIC_STORAGE_PREFIX, RebaseApiError, RebaseApiError as RebaseApiError$1, RebaseClientError, Vector, isPublicStoragePath } from "@rebasepro/types";
|
|
2
|
-
import { QueryBuilder, and, cond, or, serializeFilter, serializeLogicalCondition, serializeOrderBy } from "@rebasepro/common";
|
|
2
|
+
import { COMPOSITE_ID_SEPARATOR, QueryBuilder, and, buildCompositeId, cond, or, serializeFilter, serializeLogicalCondition, serializeOrderBy } from "@rebasepro/common";
|
|
3
3
|
import { toSnakeCase } from "@rebasepro/utils";
|
|
4
4
|
//#region src/reviver.ts
|
|
5
5
|
function rebaseReviver(_key, value) {
|
|
@@ -1035,7 +1035,7 @@ var SDKQueryBuilder = class {
|
|
|
1035
1035
|
* Listen to realtime updates matching this query.
|
|
1036
1036
|
*/
|
|
1037
1037
|
listen(onUpdate, onError) {
|
|
1038
|
-
if (!this.collection.listen) throw new Error("Listen is only available when RebaseClient is configured with a websocketUrl.");
|
|
1038
|
+
if (!this.collection.listen) throw new Error("Listen is only available when RebaseClient is configured with a websocketUrl, and not when it was created with realtime: false.");
|
|
1039
1039
|
return this.collection.listen(this.params, onUpdate, onError);
|
|
1040
1040
|
}
|
|
1041
1041
|
};
|
|
@@ -1070,6 +1070,17 @@ function createCollectionClient(transport, slug, ws) {
|
|
|
1070
1070
|
body: JSON.stringify(body)
|
|
1071
1071
|
});
|
|
1072
1072
|
},
|
|
1073
|
+
async createMany(data, options) {
|
|
1074
|
+
if (!Array.isArray(data)) throw new TypeError("createMany expects an array of records.");
|
|
1075
|
+
if (data.length === 0) return [];
|
|
1076
|
+
return (await transport.request(`${basePath}/bulk`, {
|
|
1077
|
+
method: "POST",
|
|
1078
|
+
body: JSON.stringify({
|
|
1079
|
+
rows: data,
|
|
1080
|
+
...options?.upsert ? { upsert: true } : {}
|
|
1081
|
+
})
|
|
1082
|
+
})).data || [];
|
|
1083
|
+
},
|
|
1073
1084
|
async update(id, data) {
|
|
1074
1085
|
return await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, {
|
|
1075
1086
|
method: "PUT",
|
|
@@ -1685,7 +1696,9 @@ var RebaseWebSocketClient = class {
|
|
|
1685
1696
|
const collectionSub = this.collectionSubscriptions.get(subscriptionKey);
|
|
1686
1697
|
if (collectionSub) {
|
|
1687
1698
|
const incomingRows = message.rows || [];
|
|
1688
|
-
const
|
|
1699
|
+
const updatePks = message.pks;
|
|
1700
|
+
if (updatePks) collectionSub.pks = updatePks;
|
|
1701
|
+
const rows = this.mergeRows(collectionSub.latestData, incomingRows, collectionSub.pks);
|
|
1689
1702
|
collectionSub.latestData = rows;
|
|
1690
1703
|
collectionSub.lastUpdated = Date.now();
|
|
1691
1704
|
collectionSub.isInitialDataReceived = true;
|
|
@@ -1707,12 +1720,14 @@ var RebaseWebSocketClient = class {
|
|
|
1707
1720
|
const collectionSub = this.collectionSubscriptions.get(subscriptionKey);
|
|
1708
1721
|
if (collectionSub && collectionSub.isInitialDataReceived && collectionSub.latestData) {
|
|
1709
1722
|
const patchWireEntity = message.row ?? null;
|
|
1710
|
-
const
|
|
1723
|
+
const patchMessage = message;
|
|
1724
|
+
const patchEntityId = patchMessage.id;
|
|
1725
|
+
if (patchMessage.pks) collectionSub.pks = patchMessage.pks;
|
|
1711
1726
|
const patchRow = patchWireEntity ? patchWireEntity : null;
|
|
1712
1727
|
let updated;
|
|
1713
|
-
if (patchRow === null) updated = collectionSub.latestData.filter((e) =>
|
|
1728
|
+
if (patchRow === null) updated = collectionSub.latestData.filter((e) => this.rowAddress(e, collectionSub.pks) !== String(patchEntityId));
|
|
1714
1729
|
else {
|
|
1715
|
-
const idx = collectionSub.latestData.findIndex((e) =>
|
|
1730
|
+
const idx = collectionSub.latestData.findIndex((e) => this.rowAddress(e, collectionSub.pks) === String(patchEntityId));
|
|
1716
1731
|
if (idx >= 0) {
|
|
1717
1732
|
updated = [...collectionSub.latestData];
|
|
1718
1733
|
updated[idx] = patchRow;
|
|
@@ -2062,17 +2077,39 @@ var RebaseWebSocketClient = class {
|
|
|
2062
2077
|
return val;
|
|
2063
2078
|
}
|
|
2064
2079
|
/**
|
|
2080
|
+
* The address of a row, for matching it against another copy of itself.
|
|
2081
|
+
*
|
|
2082
|
+
* A row is exactly its columns and carries no address, so it is derived
|
|
2083
|
+
* from the key columns the server named — including the ordinary case where
|
|
2084
|
+
* that key is `id`, which the server reports like any other.
|
|
2085
|
+
*
|
|
2086
|
+
* Undefined when there are no keys, which means the server could not
|
|
2087
|
+
* resolve any: such rows genuinely cannot be recognised, and guessing at a
|
|
2088
|
+
* column called `id` would be inventing an identity for a table that has
|
|
2089
|
+
* none.
|
|
2090
|
+
*/
|
|
2091
|
+
rowAddress(row, pks) {
|
|
2092
|
+
if (!pks || pks.length === 0) return void 0;
|
|
2093
|
+
const address = buildCompositeId(row, pks);
|
|
2094
|
+
if (!address || address.split(COMPOSITE_ID_SEPARATOR).every((part) => part === "")) return void 0;
|
|
2095
|
+
return address;
|
|
2096
|
+
}
|
|
2097
|
+
/**
|
|
2065
2098
|
* Merge incoming rows with cached data, preserving cached references
|
|
2066
2099
|
* for rows whose values haven't changed. This avoids unnecessary
|
|
2067
2100
|
* React re-renders when the server refetches all rows but most
|
|
2068
2101
|
* haven't actually changed.
|
|
2069
2102
|
*/
|
|
2070
|
-
mergeRows(cached, incoming) {
|
|
2103
|
+
mergeRows(cached, incoming, pks) {
|
|
2071
2104
|
if (!cached || cached.length === 0) return incoming;
|
|
2072
2105
|
const cachedById = /* @__PURE__ */ new Map();
|
|
2073
|
-
for (const row of cached)
|
|
2106
|
+
for (const row of cached) {
|
|
2107
|
+
const address = this.rowAddress(row, pks);
|
|
2108
|
+
if (address !== void 0) cachedById.set(address, row);
|
|
2109
|
+
}
|
|
2074
2110
|
return incoming.map((incomingRow) => {
|
|
2075
|
-
const
|
|
2111
|
+
const address = this.rowAddress(incomingRow, pks);
|
|
2112
|
+
const cachedRow = address === void 0 ? void 0 : cachedById.get(address);
|
|
2076
2113
|
if (!cachedRow) return incomingRow;
|
|
2077
2114
|
const normCached = this.normalizeForComparison(cachedRow);
|
|
2078
2115
|
const normIncoming = this.normalizeForComparison(incomingRow);
|
|
@@ -2084,7 +2121,7 @@ var RebaseWebSocketClient = class {
|
|
|
2084
2121
|
cached: normCached[key],
|
|
2085
2122
|
incoming: normIncoming[key]
|
|
2086
2123
|
};
|
|
2087
|
-
console.debug(`[RebaseWS] Row ${
|
|
2124
|
+
console.debug(`[RebaseWS] Row ${address} refetch mismatch:\n`, JSON.stringify(mismatches, null, 2));
|
|
2088
2125
|
}
|
|
2089
2126
|
return incomingRow;
|
|
2090
2127
|
});
|
|
@@ -2331,7 +2368,7 @@ function createRebaseClient(options) {
|
|
|
2331
2368
|
});
|
|
2332
2369
|
return storageSourcesPromise;
|
|
2333
2370
|
};
|
|
2334
|
-
const resolvedWsUrl = options.websocketUrl ?? deriveWebSocketUrl(options.baseUrl);
|
|
2371
|
+
const resolvedWsUrl = options.realtime !== false ? options.websocketUrl ?? deriveWebSocketUrl(options.baseUrl) : void 0;
|
|
2335
2372
|
let ws;
|
|
2336
2373
|
if (resolvedWsUrl) {
|
|
2337
2374
|
ws = new RebaseWebSocketClient({
|
|
@@ -2443,6 +2480,16 @@ function createRebaseClient(options) {
|
|
|
2443
2480
|
createStorageSource,
|
|
2444
2481
|
fetchStorageSources,
|
|
2445
2482
|
ws,
|
|
2483
|
+
/**
|
|
2484
|
+
* Release the realtime socket and its reconnect timer.
|
|
2485
|
+
*
|
|
2486
|
+
* Until this returns, the open socket keeps the Node event loop alive
|
|
2487
|
+
* and the process will not exit on its own. Safe to call when realtime
|
|
2488
|
+
* was never started, and safe to call twice.
|
|
2489
|
+
*/
|
|
2490
|
+
close: () => {
|
|
2491
|
+
ws?.disconnect();
|
|
2492
|
+
},
|
|
2446
2493
|
setToken: transport.setToken,
|
|
2447
2494
|
setAuthTokenGetter: transport.setAuthTokenGetter,
|
|
2448
2495
|
setOnUnauthorized: transport.setOnUnauthorized,
|