@rebasepro/types 0.13.0 → 0.13.1-canary.g06dbe5b
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/call_context.d.ts +70 -4
- package/dist/controllers/client.d.ts +47 -74
- package/dist/controllers/data.d.ts +379 -36
- package/dist/controllers/data_driver.d.ts +71 -5
- package/dist/errors.d.ts +30 -4
- package/dist/index.es.js +204 -11
- package/dist/index.es.js.map +1 -1
- package/dist/types/admin_block.d.ts +38 -1
- package/dist/types/backend.d.ts +2 -0
- package/dist/types/collections.d.ts +46 -8
- package/dist/types/cron.d.ts +50 -9
- package/dist/types/entities.d.ts +11 -0
- package/dist/types/entity_callbacks.d.ts +2 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/policy.d.ts +13 -13
- package/dist/types/properties.d.ts +31 -6
- package/dist/types/rls-functions.d.ts +84 -0
- package/dist/types/search.d.ts +231 -0
- package/package.json +2 -2
- package/src/call_context.ts +68 -4
- package/src/controllers/client.ts +47 -95
- package/src/controllers/data.ts +390 -36
- package/src/controllers/data_driver.ts +109 -8
- package/src/errors.ts +43 -4
- package/src/types/admin_block.ts +85 -0
- package/src/types/backend.ts +2 -0
- package/src/types/collections.ts +47 -8
- package/src/types/cron.ts +51 -9
- package/src/types/entities.ts +12 -0
- package/src/types/entity_callbacks.ts +2 -1
- package/src/types/index.ts +2 -0
- package/src/types/policy.ts +13 -13
- package/src/types/properties.ts +32 -6
- package/src/types/rls-functions.ts +98 -0
- package/src/types/search.ts +247 -0
package/dist/call_context.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DataDriver } from "./controllers/data_driver";
|
|
1
2
|
import type { StorageSource } from "./controllers/storage";
|
|
2
3
|
import type { RebaseClient } from "./controllers/client";
|
|
3
4
|
import type { RebaseSdkData } from "./controllers/data";
|
|
@@ -19,7 +20,16 @@ export type RebaseCallContext<USER extends User = User> = {
|
|
|
19
20
|
* The Rebase client instance.
|
|
20
21
|
* Available in all entity callbacks (beforeSave, afterSave, afterRead,
|
|
21
22
|
* beforeDelete, afterDelete) and in CollectionActionsProps via context.
|
|
22
|
-
* Use it to call backend functions, access
|
|
23
|
+
* Use it to call backend functions, access storage, send email, etc.
|
|
24
|
+
*
|
|
25
|
+
* ⚠️ **Not the same trust level as {@link data}.** Server-side this is the
|
|
26
|
+
* app singleton, so `client.dataAsAdmin` is **always** the admin-scoped
|
|
27
|
+
* plane — scoped as `{ uid: "service", roles: ["admin"] }`, so policies are
|
|
28
|
+
* evaluated against that identity rather than skipped — while {@link data},
|
|
29
|
+
* one property over, follows whoever triggered the callback. On a user
|
|
30
|
+
* request, reaching for `context.client.dataAsAdmin` silently escalates a
|
|
31
|
+
* user-scoped operation to admin. For queries in a callback use
|
|
32
|
+
* {@link data}; come here for functions, storage and email.
|
|
23
33
|
*
|
|
24
34
|
* @example
|
|
25
35
|
* // In a beforeSave callback:
|
|
@@ -35,11 +45,67 @@ export type RebaseCallContext<USER extends User = User> = {
|
|
|
35
45
|
* Unified data access — `context.data.products.create(...)`.
|
|
36
46
|
* Access any collection as a dynamic property.
|
|
37
47
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
48
|
+
* **Inherits the privilege of whatever triggered the callback.** This is not
|
|
49
|
+
* a fixed trust level, and it is the one thing to know about this accessor:
|
|
50
|
+
*
|
|
51
|
+
* - Triggered by a **user request** (REST, realtime, an admin-panel edit):
|
|
52
|
+
* user-scoped. The callback runs on the RLS-bound transaction opened for
|
|
53
|
+
* that request, so policies apply to reads *and* writes — a callback
|
|
54
|
+
* cannot see a row its caller could not.
|
|
55
|
+
* - Triggered by **`rebase.dataAsAdmin` or a cron** (the same singleton):
|
|
56
|
+
* admin-scoped, not unscoped. That driver is scoped as
|
|
57
|
+
* `{ uid: "service", roles: ["admin"] }`, so the callback still runs on an
|
|
58
|
+
* RLS-bound transaction — policies are evaluated against that identity.
|
|
59
|
+
* - Triggered by **the base driver** (auth flows, migrations): unscoped, on
|
|
60
|
+
* the owner connection, bypassing RLS.
|
|
61
|
+
*
|
|
62
|
+
* So a callback that reads a sibling row will find it when an admin task
|
|
63
|
+
* saves and may find nothing when an end user saves — without an error,
|
|
64
|
+
* because RLS filters rather than raises. Write callbacks that tolerate
|
|
65
|
+
* that, or reach for {@link client}`.dataAsAdmin` deliberately when the
|
|
66
|
+
* callback genuinely has to see what an admin may see. Note what that does
|
|
67
|
+
* *not* buy you: `policy.serverContext()` (`auth.uid() IS NULL`) is false
|
|
68
|
+
* for the service identity, so a collection whose only rule is
|
|
69
|
+
* `serverContext()` stays closed to it.
|
|
70
|
+
*
|
|
71
|
+
* Verified end-to-end against Postgres rather than asserted — see
|
|
72
|
+
* `"scopes context.data to the caller when a callback runs on a user
|
|
73
|
+
* request"` in `server-postgres`' `rls-enforcement` e2e suite. The
|
|
74
|
+
* documentation previously claimed the opposite (that callbacks always have
|
|
75
|
+
* full access), which is the unsafe direction to be wrong in.
|
|
76
|
+
*
|
|
77
|
+
* Returns flat rows (`{ id, ...columns }`), identical in *shape* to the
|
|
78
|
+
* frontend SDK client — so `context.data` in a backend callback and
|
|
79
|
+
* `client.data` in the frontend are accessed the same way (`row.title`,
|
|
80
|
+
* never `row.values.title`). Shape only: privilege differs as above.
|
|
41
81
|
*/
|
|
42
82
|
data: RebaseSdkData;
|
|
83
|
+
/**
|
|
84
|
+
* The driver executing the operation this callback is attached to.
|
|
85
|
+
*
|
|
86
|
+
* Present server-side only. Declared here because it is already public in
|
|
87
|
+
* practice — the backend has always passed it, and the callbacks guide
|
|
88
|
+
* documented `context.driver.withAuth(user)` in all six locales. The
|
|
89
|
+
* contract simply did not name it, so `buildCallContext` was cast through
|
|
90
|
+
* `as unknown as RebaseCallContext` and nothing about the object was
|
|
91
|
+
* type-checked at all.
|
|
92
|
+
*
|
|
93
|
+
* The guide no longer recommends `withAuth` — {@link data} is already
|
|
94
|
+
* user-scoped on a user request, so the manual re-scoping it described was
|
|
95
|
+
* answering a problem that did not exist. The field stays declared rather
|
|
96
|
+
* than removed: it is on the runtime object, dropping it would break anyone
|
|
97
|
+
* who found it, and a named optional is better than a silent extra.
|
|
98
|
+
*
|
|
99
|
+
* `withAuth` is not on {@link DataDriver} because not every engine supports
|
|
100
|
+
* RLS scoping; it is narrowed here, and left optional so a driver without it
|
|
101
|
+
* is a compile-time absence rather than a runtime surprise.
|
|
102
|
+
*/
|
|
103
|
+
driver?: DataDriver & {
|
|
104
|
+
withAuth?(user: {
|
|
105
|
+
uid: string;
|
|
106
|
+
roles?: string[];
|
|
107
|
+
}): Promise<DataDriver>;
|
|
108
|
+
};
|
|
43
109
|
/**
|
|
44
110
|
* Used storage implementation
|
|
45
111
|
*/
|
|
@@ -296,18 +296,31 @@ export interface RebaseClient<DB = unknown> {
|
|
|
296
296
|
/** Unified Data access layer */
|
|
297
297
|
data: RebaseSdkData<DB>;
|
|
298
298
|
/**
|
|
299
|
-
* Admin-scoped
|
|
299
|
+
* Admin-scoped data accessor — **not** an RLS bypass.
|
|
300
300
|
*
|
|
301
301
|
* Present on the **server** singleton only (see {@link RebaseServerClient}).
|
|
302
|
-
* It runs
|
|
303
|
-
*
|
|
304
|
-
*
|
|
302
|
+
* It runs as the service identity `{ uid: "service", roles: ["admin"] }`,
|
|
303
|
+
* and the driver is scoped with `withAuth()` at boot, so every read and
|
|
304
|
+
* write runs in a transaction that has switched to the restricted
|
|
305
|
+
* `rebase_user` role with `app.uid = 'service'`: policies are evaluated,
|
|
306
|
+
* against that identity. This is the correct tool for trusted background
|
|
307
|
+
* work (cron jobs, migrations, service-to-service tasks).
|
|
308
|
+
*
|
|
309
|
+
* Two consequences the name does not suggest:
|
|
310
|
+
*
|
|
311
|
+
* - `policy.serverContext()` compiles to `auth.uid() IS NULL` and is
|
|
312
|
+
* therefore **false** here. A collection with `disableDefaultPolicies:
|
|
313
|
+
* true` whose only rule is `serverContext()` refuses these writes
|
|
314
|
+
* (`42501`) and returns zero rows — HTTP 200, empty — for these reads.
|
|
315
|
+
* - Its reach equals an `admin`-roled application user's reach. It is not a
|
|
316
|
+
* private channel. The true bypass is {@link sql}, which runs on the
|
|
317
|
+
* owner connection and never goes through `withAuth`.
|
|
305
318
|
*
|
|
306
319
|
* ⚠️ **Do NOT use it to serve user-facing data.** Inside a request handler,
|
|
307
320
|
* user-scoped queries must go through the request-scoped driver
|
|
308
|
-
* (`c.var.driver`), which carries the caller's identity
|
|
309
|
-
*
|
|
310
|
-
*
|
|
321
|
+
* (`c.var.driver`), which carries the caller's identity. Reaching for
|
|
322
|
+
* `dataAsAdmin` (or its alias {@link data}) in a request handler serves
|
|
323
|
+
* every caller whatever an admin may see.
|
|
311
324
|
*
|
|
312
325
|
* Undefined in the browser SDK.
|
|
313
326
|
*/
|
|
@@ -371,7 +384,22 @@ export interface RebaseClient<DB = unknown> {
|
|
|
371
384
|
setOnUnauthorized?(handler: () => Promise<boolean>): void;
|
|
372
385
|
/** Resolve the current auth token */
|
|
373
386
|
resolveToken?(): Promise<string | null>;
|
|
374
|
-
/**
|
|
387
|
+
/**
|
|
388
|
+
* POST to an arbitrary path on the backend — the escape hatch, not the way
|
|
389
|
+
* to call a function.
|
|
390
|
+
*
|
|
391
|
+
* For a custom function use {@link functions}`.invoke(name, payload)`: it
|
|
392
|
+
* targets `/functions/<name>`, takes a method and sub-path, and returns the
|
|
393
|
+
* response body as sent. This posts wherever you point it and **unwraps**:
|
|
394
|
+
* it returns `res.data` when the response has a `data` property and the
|
|
395
|
+
* whole envelope otherwise — so an endpoint that legitimately answers
|
|
396
|
+
* `{ data: null }` hands back the envelope rather than `null`. Two ways to
|
|
397
|
+
* reach a function with two different response contracts is a trap; this is
|
|
398
|
+
* the one that exists for paths `invoke` cannot express.
|
|
399
|
+
*
|
|
400
|
+
* @internal Prefer `functions.invoke()`. Kept public because a backend can
|
|
401
|
+
* mount routes outside `/functions`, and nothing else reaches those.
|
|
402
|
+
*/
|
|
375
403
|
call?<T = unknown>(endpoint: string, payload?: unknown): Promise<T>;
|
|
376
404
|
/**
|
|
377
405
|
* Execute raw SQL against the database.
|
|
@@ -390,17 +418,20 @@ export interface RebaseClient<DB = unknown> {
|
|
|
390
418
|
* the admin-scoped {@link dataAsAdmin} accessor, raw {@link sql}, and the
|
|
391
419
|
* {@link email} service are all present (non-optional).
|
|
392
420
|
*
|
|
393
|
-
* **Trust levels.** {@link dataAsAdmin} is the admin-scoped
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
421
|
+
* **Trust levels.** {@link dataAsAdmin} is the admin-scoped driver — scoped as
|
|
422
|
+
* `{ uid: "service", roles: ["admin"] }`, so policies are still evaluated
|
|
423
|
+
* against that identity rather than skipped — and it is the only name for it
|
|
424
|
+
* here: the `data` alias that used to sit beside it is deliberately `Omit`ted
|
|
425
|
+
* from {@link RebaseClient} so the privilege has to be spelled out at every
|
|
426
|
+
* call site. {@link sql} is the unconditional bypass: raw SQL on the owner
|
|
427
|
+
* connection, no policies. For user-scoped queries inside a request handler use
|
|
428
|
+
* the request-scoped driver (`c.var.driver`) instead — never `dataAsAdmin`.
|
|
399
429
|
*/
|
|
400
430
|
export interface RebaseServerClient<DB = unknown> extends Omit<RebaseClient<DB>, "data"> {
|
|
401
431
|
/**
|
|
402
|
-
* Admin-scoped
|
|
403
|
-
* See {@link RebaseClient.dataAsAdmin}
|
|
432
|
+
* Admin-scoped data accessor (RLS is evaluated as the service identity, not
|
|
433
|
+
* skipped). Always present server-side. See {@link RebaseClient.dataAsAdmin}
|
|
434
|
+
* for the full safety contract.
|
|
404
435
|
*/
|
|
405
436
|
dataAsAdmin: RebaseSdkData<DB>;
|
|
406
437
|
/**
|
|
@@ -419,64 +450,6 @@ export interface RebaseServerClient<DB = unknown> extends Omit<RebaseClient<DB>,
|
|
|
419
450
|
params?: unknown[];
|
|
420
451
|
}): Promise<Record<string, unknown>[]>;
|
|
421
452
|
}
|
|
422
|
-
/**
|
|
423
|
-
* The browser-side Rebase surface — the shape produced by
|
|
424
|
-
* `createRebaseClient()` in `@rebasepro/client`.
|
|
425
|
-
*
|
|
426
|
-
* Its {@link data} accessor is **user-scoped**: every call carries the signed-in
|
|
427
|
-
* user's token, so backend RLS policies apply. It deliberately omits the
|
|
428
|
-
* server-only members — there is no `sql`, no `email`, and no
|
|
429
|
-
* `dataAsAdmin`, so the RLS-bypassing accessor can never be reached from
|
|
430
|
-
* browser code.
|
|
431
|
-
*/
|
|
432
|
-
export interface RebaseBrowserClient<DB = unknown> {
|
|
433
|
-
/** User-scoped data access layer (carries the signed-in user's token). */
|
|
434
|
-
data: RebaseSdkData<DB>;
|
|
435
|
-
/** Unified Authentication layer */
|
|
436
|
-
auth: AuthClient;
|
|
437
|
-
/** Unified Storage layer (default storage source, backward-compatible) */
|
|
438
|
-
storage?: StorageSource;
|
|
439
|
-
/** Registry of all named storage sources for multi-backend support */
|
|
440
|
-
storageRegistry?: StorageSourceRegistry;
|
|
441
|
-
/** Build a server-backed {@link StorageSource} for a named storage source. */
|
|
442
|
-
createStorageSource?(storageId: string): StorageSource;
|
|
443
|
-
/** Discover the storage sources declared on the backend. */
|
|
444
|
-
fetchStorageSources?(): Promise<StorageSourceDefinition[]>;
|
|
445
|
-
/** Admin API for user management */
|
|
446
|
-
admin?: AdminAPI;
|
|
447
|
-
/** Cron job management API */
|
|
448
|
-
cron?: CronAPI;
|
|
449
|
-
/** Database backup management API */
|
|
450
|
-
backups?: BackupsAPI;
|
|
451
|
-
/** Custom backend functions API */
|
|
452
|
-
functions?: FunctionsAPI;
|
|
453
|
-
/** Service API keys management API */
|
|
454
|
-
apiKeys?: ApiKeysAPI;
|
|
455
|
-
/** Base HTTP URL of the backend server */
|
|
456
|
-
baseUrl?: string;
|
|
457
|
-
/**
|
|
458
|
-
* The path every API route is mounted under, appended to {@link baseUrl}.
|
|
459
|
-
*
|
|
460
|
-
* `"/api"` unless the backend was configured with a different `basePath`
|
|
461
|
-
* and the client told to match. Exposed because code that builds a URL by
|
|
462
|
-
* hand — rather than going through the client's own methods — otherwise has
|
|
463
|
-
* to guess, and guessing `/api` is wrong for exactly the projects that set
|
|
464
|
-
* the option.
|
|
465
|
-
*/
|
|
466
|
-
apiPath?: string;
|
|
467
|
-
/** WebSocket client for realtime subscriptions */
|
|
468
|
-
ws?: RebaseWebSocket;
|
|
469
|
-
/** Set the auth token for subsequent requests */
|
|
470
|
-
setToken?(token: string | null): void;
|
|
471
|
-
/** Set a function that lazily resolves the auth token */
|
|
472
|
-
setAuthTokenGetter?(getter: () => Promise<string | null>): void;
|
|
473
|
-
/** Set handler called when a request returns 401 */
|
|
474
|
-
setOnUnauthorized?(handler: () => Promise<boolean>): void;
|
|
475
|
-
/** Resolve the current auth token */
|
|
476
|
-
resolveToken?(): Promise<string | null>;
|
|
477
|
-
/** Make a raw HTTP call to the backend */
|
|
478
|
-
call?<T = unknown>(endpoint: string, payload?: unknown): Promise<T>;
|
|
479
|
-
}
|
|
480
453
|
/**
|
|
481
454
|
* Client-side registry for managing multiple storage sources.
|
|
482
455
|
*
|