@rebasepro/client 0.11.1-canary.gfd39654 → 0.12.1-canary.g181d0fe

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.
@@ -1,42 +1,14 @@
1
1
  import type { Transport } from "./transport";
2
- /** A single permission entry scoping an API key to a collection and its allowed operations. */
3
- export interface ApiKeyPermission {
4
- collection: string;
5
- operations: ("read" | "write" | "delete")[];
6
- }
7
- /** An API key with the secret portion masked (returned by list / get / update). */
8
- export interface ApiKeyMasked {
9
- id: string;
10
- name: string;
11
- key_prefix: string;
12
- permissions: ApiKeyPermission[];
13
- admin: boolean;
14
- rate_limit: number | null;
15
- created_by: string;
16
- created_at: string;
17
- updated_at: string;
18
- last_used_at: string | null;
19
- expires_at: string | null;
20
- revoked_at: string | null;
21
- }
22
- /** An API key including the full secret (returned only on creation). */
23
- export interface ApiKeyWithSecret extends ApiKeyMasked {
24
- key: string;
25
- }
26
- /** Payload for creating a new API key. */
27
- export interface CreateApiKeyRequest {
28
- name: string;
29
- permissions: ApiKeyPermission[];
30
- rate_limit?: number | null;
31
- expires_at?: string | null;
32
- }
33
- /** Payload for updating an existing API key. */
34
- export interface UpdateApiKeyRequest {
35
- name?: string;
36
- permissions?: ApiKeyPermission[];
37
- rate_limit?: number | null;
38
- expires_at?: string | null;
39
- }
2
+ /**
3
+ * These were re-declared here, under a comment saying they lived in the server
4
+ * package rather than in `@rebasepro/types`. That stopped being true, and the
5
+ * copy drifted: it never gained `admin`, the flag that grants a key the `admin`
6
+ * role — admin routes plus the RLS `default_admin` policies — so the SDK could
7
+ * describe every kind of key except a privileged one, and
8
+ * `createKey({ …, admin: true })` was an excess-property error.
9
+ */
10
+ export type { ApiKeyPermission, ApiKeyMasked, ApiKeyWithSecret, CreateApiKeyRequest, UpdateApiKeyRequest } from "@rebasepro/types";
11
+ import type { ApiKeyMasked, ApiKeyWithSecret, CreateApiKeyRequest, UpdateApiKeyRequest } from "@rebasepro/types";
40
12
  /** Options for the `createApiKeys` factory. */
41
13
  export interface CreateApiKeysOptions {
42
14
  apiKeysPath?: string;
package/dist/auth.d.ts CHANGED
@@ -180,6 +180,7 @@ export declare function createAuth(transport: Transport, options?: CreateAuthOpt
180
180
  getAuthConfig: () => Promise<AuthConfig>;
181
181
  getSession: () => RebaseSession | null;
182
182
  onAuthStateChange: (callback: (event: AuthChangeEvent, session: RebaseSession | null) => void) => () => boolean;
183
+ canRestoreSession: () => boolean;
183
184
  isInitialized: () => Promise<void>;
184
185
  };
185
186
  export interface CookieStorageOptions {
@@ -27,14 +27,8 @@ export interface FunctionsClient {
27
27
  */
28
28
  invoke<T = unknown>(name: string, payload?: unknown, options?: FunctionInvokeOptions): Promise<T>;
29
29
  }
30
- export interface FunctionInvokeOptions {
31
- /** HTTP method — defaults to `"POST"`. */
32
- method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
33
- /** Sub-path appended after the function name, e.g. `"status/123"`. */
34
- path?: string;
35
- /** Extra headers merged into the request (auth is still injected automatically). */
36
- headers?: Record<string, string>;
37
- }
30
+ export type { FunctionInvokeOptions } from "@rebasepro/types";
31
+ import type { FunctionInvokeOptions } from "@rebasepro/types";
38
32
  /**
39
33
  * Create a `FunctionsClient` backed by the given transport.
40
34
  *
package/dist/index.d.ts CHANGED
@@ -15,6 +15,9 @@ export { RebaseClientError } from "./errors";
15
15
  export type { RebaseClientConfig, FindParams, FindResponse } from "./transport";
16
16
  export type { CollectionClient } from "./collection";
17
17
  export type { FindResult, SDKCollectionClient, SDKQueryBuilderInterface, PaginationMeta } from "@rebasepro/types";
18
+ export type { IterateParams, FindAllParams, PageWalkOptions, CursorSpec } from "@rebasepro/types";
19
+ export { RebasePaginationError } from "@rebasepro/common";
20
+ export type { PaginationErrorCode } from "@rebasepro/common";
18
21
  export { QueryBuilder, or, and, cond } from "@rebasepro/common";
19
22
  export { createCookieStorage, createMemoryStorage } from "./auth";
20
23
  export type { AuthConfig, AuthStorage, CookieStorageOptions, CreateAuthOptions } from "./auth";
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, toCanonicalOp } from "@rebasepro/types";
2
- import { COMPOSITE_ID_SEPARATOR, QueryBuilder, and, buildCompositeId, cond, or, serializeFilter, serializeLogicalCondition, serializeOrderBy } from "@rebasepro/common";
2
+ import { COMPOSITE_ID_SEPARATOR, QueryBuilder, RebasePaginationError, and, buildCompositeId, collectAllPages, cond, or, paginateFind, 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) {
@@ -30,6 +30,25 @@ function rebaseReviver(_key, value) {
30
30
  }
31
31
  //#endregion
32
32
  //#region src/transport.ts
33
+ /**
34
+ * True when there is no browser to have signed a user in — a Node script, a
35
+ * cron job, an edge worker.
36
+ *
37
+ * Anonymous is an ordinary, correct state in a browser: before sign-in, on a
38
+ * marketing page, for public reads. Warning there would be noise that teaches
39
+ * people to ignore warnings, so the guard is off entirely. This uses the same
40
+ * `typeof window` test as {@link resolveBaseUrl}, and additionally treats a
41
+ * defined `document` as a browser so an SSR shim or test harness that installs
42
+ * only one of the two is still excluded.
43
+ */
44
+ function isServerLikeEnvironment() {
45
+ return typeof window === "undefined" && typeof document === "undefined";
46
+ }
47
+ /**
48
+ * Emitted once per client. Kept as a constant so the wording is testable and
49
+ * greppable — this is the string a user will paste into a search.
50
+ */
51
+ var ANONYMOUS_SERVER_CLIENT_WARNING = "[rebase] This client was created outside a browser with no credential — no `token`, no auth token getter, and no cookie auth flow — so every request runs as an anonymous caller. Row-level security will return only publicly readable rows, which is usually nothing and occasionally the wrong thing. Inside a cron or function handler, use the `client` you were handed instead of building a new one: its data plane is already admin-scoped. In a standalone script or job, pass the service key as `token`. If you really do want anonymous access, pass `anonymous: true` to silence this.";
33
52
  function buildQueryString(params) {
34
53
  if (!params) return "";
35
54
  const parts = [];
@@ -76,12 +95,31 @@ function resolveBaseUrl(configured) {
76
95
  if (typeof window !== "undefined" && window.location?.origin) return window.location.origin;
77
96
  return "";
78
97
  }
79
- function createTransport(config) {
98
+ function createTransport(config, environment) {
80
99
  const fetchFn = config.fetch || globalThis.fetch;
81
100
  const apiPath = config.apiPath || "/api";
82
101
  let token = config.token;
83
102
  let tokenGetter;
84
103
  let onUnauthorizedHandler = config.onUnauthorized;
104
+ /** Once per client, never per request — log spam is its own bug. */
105
+ let anonymousWarningIssued = false;
106
+ /**
107
+ * Warn a server-side caller that it built a client that can only ever be
108
+ * anonymous. Deliberately checked at the *first request* rather than at
109
+ * construction: `setToken()` / `setAuthTokenGetter()` and a server-side
110
+ * `auth.signIn…()` (which calls `transport.setToken`) all land after the
111
+ * constructor, and warning at construction would fire on every one of them.
112
+ */
113
+ function warnIfAnonymousServerClient(activeToken) {
114
+ if (anonymousWarningIssued) return;
115
+ if (activeToken) return;
116
+ if (tokenGetter) return;
117
+ if (config.anonymous) return;
118
+ if (environment?.credentialOutOfBand) return;
119
+ if (!isServerLikeEnvironment()) return;
120
+ anonymousWarningIssued = true;
121
+ console.warn(ANONYMOUS_SERVER_CLIENT_WARNING);
122
+ }
85
123
  function getHeaders(activeToken, init) {
86
124
  return {
87
125
  "Content-Type": "application/json",
@@ -96,6 +134,7 @@ function createTransport(config) {
96
134
  const fetched = await tokenGetter();
97
135
  if (fetched !== null && fetched !== void 0) activeToken = fetched;
98
136
  } catch (e) {}
137
+ warnIfAnonymousServerClient(activeToken);
99
138
  const headers = getHeaders(activeToken, init);
100
139
  if (init?.body instanceof FormData) delete headers["Content-Type"];
101
140
  const res = await fetchFn(url, {
@@ -868,6 +907,7 @@ function createAuth(transport, options) {
868
907
  getAuthConfig,
869
908
  getSession,
870
909
  onAuthStateChange,
910
+ canRestoreSession: () => persistSession || authFlowMode === "cookie",
871
911
  isInitialized: () => isInitialized
872
912
  };
873
913
  }
@@ -1184,6 +1224,12 @@ function createCollectionClient(transport, slug, ws) {
1184
1224
  meta: raw.meta
1185
1225
  };
1186
1226
  },
1227
+ iterate(params) {
1228
+ return paginateFind((p) => client.find(p), params, slug);
1229
+ },
1230
+ findAll(params) {
1231
+ return collectAllPages((p) => client.find(p), params, slug);
1232
+ },
1187
1233
  async findById(id) {
1188
1234
  try {
1189
1235
  const raw = await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, { method: "GET" });
@@ -1373,7 +1419,8 @@ function createCollectionClient(transport, slug, ws) {
1373
1419
  function createFunctionsClient(transport) {
1374
1420
  return { async invoke(name, payload, options) {
1375
1421
  const method = options?.method ?? "POST";
1376
- const subPath = options?.path ? `/${options.path.replace(/^\//, "")}` : "";
1422
+ const rawPath = options?.path;
1423
+ const subPath = rawPath ? /^[?#]/.test(rawPath) ? rawPath : `/${rawPath.replace(/^\//, "")}` : "";
1377
1424
  const routePath = `/functions/${encodeURIComponent(name)}${subPath}`;
1378
1425
  const init = { method };
1379
1426
  if (payload !== void 0 && method !== "GET") init.body = JSON.stringify(payload);
@@ -1587,7 +1634,7 @@ function extractMessageError(message) {
1587
1634
  * Fire-and-forget (the server sends no response envelope), and exempt from the
1588
1635
  * client-side auth gate — a public channel is usable without an account.
1589
1636
  */
1590
- var CHANNEL_MESSAGE_TYPES = new Set([
1637
+ var CHANNEL_MESSAGE_TYPES = /* @__PURE__ */ new Set([
1591
1638
  "join_channel",
1592
1639
  "leave_channel",
1593
1640
  "broadcast",
@@ -1616,6 +1663,17 @@ var RebaseWebSocketClient = class {
1616
1663
  /** Set by `close()`. Blocks any later operation from silently redialling. */
1617
1664
  closedByCaller = false;
1618
1665
  /**
1666
+ * Set when the backoff budget ran out, cleared by anything that earns a
1667
+ * fresh one.
1668
+ *
1669
+ * Unlike {@link closedByCaller} this is not final — nobody *asked* for the
1670
+ * socket to stay down. Five attempts with exponential backoff is about a
1671
+ * minute, which a laptop lid, a wifi handover or a backend rollout all
1672
+ * exceed routinely; treating that as permanent meant realtime silently
1673
+ * stopped for the rest of the page's life, with a reload the only cure.
1674
+ */
1675
+ gaveUp = false;
1676
+ /**
1619
1677
  * Whether a socket exists at all (open or still opening).
1620
1678
  *
1621
1679
  * Lets callers distinguish "authenticate the live socket" from "there is
@@ -1688,10 +1746,30 @@ var RebaseWebSocketClient = class {
1688
1746
  }
1689
1747
  return;
1690
1748
  }
1749
+ this.installOnlineListener();
1691
1750
  if (this.ws || this.reconnectTimeout) return;
1751
+ if (this.gaveUp) {
1752
+ this.gaveUp = false;
1753
+ this.reconnectAttempts = 0;
1754
+ }
1692
1755
  this.initWebSocket();
1693
1756
  }
1694
1757
  /**
1758
+ * The browser says the network is back — the usual reason the budget ran
1759
+ * out in the first place. Registered lazily so a Node client, or a page
1760
+ * that never subscribes, adds no listener.
1761
+ */
1762
+ installOnlineListener() {
1763
+ if (this.onlineListener || typeof window === "undefined" || typeof window.addEventListener !== "function") return;
1764
+ this.onlineListener = () => {
1765
+ if (this.closedByCaller || !this.gaveUp) return;
1766
+ console.debug("Network is back — retrying the realtime connection");
1767
+ this.ensureConnected();
1768
+ };
1769
+ window.addEventListener("online", this.onlineListener);
1770
+ }
1771
+ onlineListener = null;
1772
+ /**
1695
1773
  * Authenticate the WebSocket connection
1696
1774
  */
1697
1775
  async authenticate(token) {
@@ -1749,6 +1827,10 @@ var RebaseWebSocketClient = class {
1749
1827
  */
1750
1828
  disconnect(permanent = false) {
1751
1829
  if (permanent) this.closedByCaller = true;
1830
+ if (permanent && this.onlineListener && typeof window !== "undefined") {
1831
+ window.removeEventListener("online", this.onlineListener);
1832
+ this.onlineListener = null;
1833
+ }
1752
1834
  this.isAuthenticated = false;
1753
1835
  this.authPromise = null;
1754
1836
  if (this.reconnectTimeout) {
@@ -1773,7 +1855,8 @@ var RebaseWebSocketClient = class {
1773
1855
  this.ws = null;
1774
1856
  }
1775
1857
  try {
1776
- this.ws = new this.WebSocketConstructor(this.websocketUrl);
1858
+ const socket = new this.WebSocketConstructor(this.websocketUrl);
1859
+ this.ws = socket;
1777
1860
  this.ws.onopen = async () => {
1778
1861
  console.debug("Connected to PostgreSQL backend");
1779
1862
  const wasReconnect = this.reconnectAttempts > 0;
@@ -1803,6 +1886,7 @@ var RebaseWebSocketClient = class {
1803
1886
  };
1804
1887
  this.ws.onclose = () => {
1805
1888
  console.debug("Disconnected from PostgreSQL backend");
1889
+ if (this.ws === socket) this.ws = null;
1806
1890
  this.isConnected = false;
1807
1891
  this.isAuthenticated = false;
1808
1892
  this.authPromise = null;
@@ -1838,6 +1922,7 @@ var RebaseWebSocketClient = class {
1838
1922
  attemptReconnect() {
1839
1923
  if (this.reconnectAttempts >= this.maxReconnectAttempts) {
1840
1924
  console.error("Max reconnection attempts reached");
1925
+ this.gaveUp = true;
1841
1926
  this.failAllPendingSubscriptions(new RebaseApiError$1("Connection lost", { code: "CONNECTION_LOST" }));
1842
1927
  return;
1843
1928
  }
@@ -2380,7 +2465,7 @@ var RebaseWebSocketClient = class {
2380
2465
  if (this.deepEqual(normCached, normIncoming)) return cachedRow;
2381
2466
  else {
2382
2467
  const mismatches = {};
2383
- const allKeys = new Set([...Object.keys(normCached), ...Object.keys(normIncoming)]);
2468
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(normCached), ...Object.keys(normIncoming)]);
2384
2469
  for (const key of allKeys) if (!this.deepEqual(normCached[key], normIncoming[key])) mismatches[key] = {
2385
2470
  cached: normCached[key],
2386
2471
  incoming: normIncoming[key]
@@ -3183,7 +3268,7 @@ function isNetworkError(error) {
3183
3268
  * bug the same payload will hit again than a blip, and retrying it forever
3184
3269
  * jams every write behind it.
3185
3270
  */
3186
- var RETRYABLE_STATUSES = new Set([
3271
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([
3187
3272
  408,
3188
3273
  425,
3189
3274
  429,
@@ -4008,6 +4093,8 @@ var OfflineManager = class {
4008
4093
  meta: answer.meta
4009
4094
  };
4010
4095
  },
4096
+ iterate: (params) => paginateFind((p) => wrapped.find(p), params, slug),
4097
+ findAll: (params) => collectAllPages((p) => wrapped.find(p), params, slug),
4011
4098
  findById: async (id) => {
4012
4099
  await this.ensureCollection(slug);
4013
4100
  if (this.connectivity.shouldAttempt()) try {
@@ -5077,7 +5164,7 @@ function deriveWebSocketUrl(baseUrl) {
5077
5164
  return baseUrl.replace(/^https?:\/\//i, (match) => match.toLowerCase() === "https://" ? "wss://" : "ws://").replace(/\/$/, "");
5078
5165
  }
5079
5166
  function createRebaseClient(options) {
5080
- const transport = createTransport(options);
5167
+ const transport = createTransport(options, { credentialOutOfBand: options.auth?.authFlowMode === "cookie" });
5081
5168
  const auth = createAuth(transport, options.auth);
5082
5169
  const admin = createAdmin(transport, options.admin);
5083
5170
  const cron = createCron(transport, options.cron);
@@ -5248,6 +5335,7 @@ channel: (name, options) => {
5248
5335
  setOnUnauthorized: transport.setOnUnauthorized,
5249
5336
  resolveToken: transport.resolveToken,
5250
5337
  baseUrl: transport.baseUrl,
5338
+ apiPath: transport.apiPath,
5251
5339
  collection,
5252
5340
  call: async (endpoint, payload) => {
5253
5341
  const prefix = endpoint.startsWith("/") ? "" : "/";
@@ -5262,6 +5350,6 @@ channel: (name, options) => {
5262
5350
  };
5263
5351
  }
5264
5352
  //#endregion
5265
- export { MemoryOfflineStore, QueryBuilder, RebaseApiError, RebaseClientError, RebaseRealtimeChannel, RebaseWebSocketClient, and, cond, createBackups, createCookieStorage, createMemoryStorage, createRebaseClient, isOfflineError, or };
5353
+ export { MemoryOfflineStore, QueryBuilder, RebaseApiError, RebaseClientError, RebasePaginationError, RebaseRealtimeChannel, RebaseWebSocketClient, and, cond, createBackups, createCookieStorage, createMemoryStorage, createRebaseClient, isOfflineError, or };
5266
5354
 
5267
5355
  //# sourceMappingURL=index.es.js.map