@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 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 rows = this.mergeRows(collectionSub.latestData, incomingRows);
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 patchEntityId = message.id;
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) => String(e.id) !== String(patchEntityId));
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) => String(e.id) === String(patchRow.id));
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) cachedById.set(row.id, row);
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 cachedRow = cachedById.get(incomingRow.id);
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 ${incomingRow.id} refetch mismatch:\n`, JSON.stringify(mismatches, null, 2));
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,