@rebasepro/client 0.11.1-canary.gfd39654 → 0.12.1-canary.gdfba2a1
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/api-keys.d.ts +10 -38
- package/dist/functions.d.ts +2 -8
- package/dist/index.d.ts +3 -0
- package/dist/index.es.js +56 -8
- package/dist/index.es.js.map +1 -1
- package/dist/realtime-channel.d.ts +8 -8
- package/dist/transport.d.ts +35 -1
- package/package.json +8 -8
- package/src/anonymous-client-guard.test.ts +190 -0
- package/src/api-keys.ts +21 -44
- package/src/collection.test.ts +1 -1
- package/src/collection.ts +14 -0
- package/src/functions.ts +11 -9
- package/src/index.ts +10 -1
- package/src/offline-idb-store.test.ts +35 -0
- package/src/offline-integration.test.ts +5 -0
- package/src/offline.ts +9 -1
- package/src/realtime-channel.ts +8 -8
- package/src/transport.ts +80 -1
package/dist/api-keys.d.ts
CHANGED
|
@@ -1,42 +1,14 @@
|
|
|
1
1
|
import type { Transport } from "./transport";
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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/functions.d.ts
CHANGED
|
@@ -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
|
|
31
|
-
|
|
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, {
|
|
@@ -1184,6 +1223,12 @@ function createCollectionClient(transport, slug, ws) {
|
|
|
1184
1223
|
meta: raw.meta
|
|
1185
1224
|
};
|
|
1186
1225
|
},
|
|
1226
|
+
iterate(params) {
|
|
1227
|
+
return paginateFind((p) => client.find(p), params, slug);
|
|
1228
|
+
},
|
|
1229
|
+
findAll(params) {
|
|
1230
|
+
return collectAllPages((p) => client.find(p), params, slug);
|
|
1231
|
+
},
|
|
1187
1232
|
async findById(id) {
|
|
1188
1233
|
try {
|
|
1189
1234
|
const raw = await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, { method: "GET" });
|
|
@@ -1373,7 +1418,8 @@ function createCollectionClient(transport, slug, ws) {
|
|
|
1373
1418
|
function createFunctionsClient(transport) {
|
|
1374
1419
|
return { async invoke(name, payload, options) {
|
|
1375
1420
|
const method = options?.method ?? "POST";
|
|
1376
|
-
const
|
|
1421
|
+
const rawPath = options?.path;
|
|
1422
|
+
const subPath = rawPath ? /^[?#]/.test(rawPath) ? rawPath : `/${rawPath.replace(/^\//, "")}` : "";
|
|
1377
1423
|
const routePath = `/functions/${encodeURIComponent(name)}${subPath}`;
|
|
1378
1424
|
const init = { method };
|
|
1379
1425
|
if (payload !== void 0 && method !== "GET") init.body = JSON.stringify(payload);
|
|
@@ -1587,7 +1633,7 @@ function extractMessageError(message) {
|
|
|
1587
1633
|
* Fire-and-forget (the server sends no response envelope), and exempt from the
|
|
1588
1634
|
* client-side auth gate — a public channel is usable without an account.
|
|
1589
1635
|
*/
|
|
1590
|
-
var CHANNEL_MESSAGE_TYPES = new Set([
|
|
1636
|
+
var CHANNEL_MESSAGE_TYPES = /* @__PURE__ */ new Set([
|
|
1591
1637
|
"join_channel",
|
|
1592
1638
|
"leave_channel",
|
|
1593
1639
|
"broadcast",
|
|
@@ -2380,7 +2426,7 @@ var RebaseWebSocketClient = class {
|
|
|
2380
2426
|
if (this.deepEqual(normCached, normIncoming)) return cachedRow;
|
|
2381
2427
|
else {
|
|
2382
2428
|
const mismatches = {};
|
|
2383
|
-
const allKeys = new Set([...Object.keys(normCached), ...Object.keys(normIncoming)]);
|
|
2429
|
+
const allKeys = /* @__PURE__ */ new Set([...Object.keys(normCached), ...Object.keys(normIncoming)]);
|
|
2384
2430
|
for (const key of allKeys) if (!this.deepEqual(normCached[key], normIncoming[key])) mismatches[key] = {
|
|
2385
2431
|
cached: normCached[key],
|
|
2386
2432
|
incoming: normIncoming[key]
|
|
@@ -3183,7 +3229,7 @@ function isNetworkError(error) {
|
|
|
3183
3229
|
* bug the same payload will hit again than a blip, and retrying it forever
|
|
3184
3230
|
* jams every write behind it.
|
|
3185
3231
|
*/
|
|
3186
|
-
var RETRYABLE_STATUSES = new Set([
|
|
3232
|
+
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([
|
|
3187
3233
|
408,
|
|
3188
3234
|
425,
|
|
3189
3235
|
429,
|
|
@@ -4008,6 +4054,8 @@ var OfflineManager = class {
|
|
|
4008
4054
|
meta: answer.meta
|
|
4009
4055
|
};
|
|
4010
4056
|
},
|
|
4057
|
+
iterate: (params) => paginateFind((p) => wrapped.find(p), params, slug),
|
|
4058
|
+
findAll: (params) => collectAllPages((p) => wrapped.find(p), params, slug),
|
|
4011
4059
|
findById: async (id) => {
|
|
4012
4060
|
await this.ensureCollection(slug);
|
|
4013
4061
|
if (this.connectivity.shouldAttempt()) try {
|
|
@@ -5077,7 +5125,7 @@ function deriveWebSocketUrl(baseUrl) {
|
|
|
5077
5125
|
return baseUrl.replace(/^https?:\/\//i, (match) => match.toLowerCase() === "https://" ? "wss://" : "ws://").replace(/\/$/, "");
|
|
5078
5126
|
}
|
|
5079
5127
|
function createRebaseClient(options) {
|
|
5080
|
-
const transport = createTransport(options);
|
|
5128
|
+
const transport = createTransport(options, { credentialOutOfBand: options.auth?.authFlowMode === "cookie" });
|
|
5081
5129
|
const auth = createAuth(transport, options.auth);
|
|
5082
5130
|
const admin = createAdmin(transport, options.admin);
|
|
5083
5131
|
const cron = createCron(transport, options.cron);
|
|
@@ -5262,6 +5310,6 @@ channel: (name, options) => {
|
|
|
5262
5310
|
};
|
|
5263
5311
|
}
|
|
5264
5312
|
//#endregion
|
|
5265
|
-
export { MemoryOfflineStore, QueryBuilder, RebaseApiError, RebaseClientError, RebaseRealtimeChannel, RebaseWebSocketClient, and, cond, createBackups, createCookieStorage, createMemoryStorage, createRebaseClient, isOfflineError, or };
|
|
5313
|
+
export { MemoryOfflineStore, QueryBuilder, RebaseApiError, RebaseClientError, RebasePaginationError, RebaseRealtimeChannel, RebaseWebSocketClient, and, cond, createBackups, createCookieStorage, createMemoryStorage, createRebaseClient, isOfflineError, or };
|
|
5266
5314
|
|
|
5267
5315
|
//# sourceMappingURL=index.es.js.map
|