@protobase/server 0.30.2 → 0.31.0
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/admin-options.d.ts +2 -2
- package/dist/api-keys/caller-settings.js +1 -1
- package/dist/create-admin.d.ts +2 -2
- package/dist/create-admin.js +6 -5
- package/dist/files/options.d.ts +2 -2
- package/dist/files/runtime.js +2 -2
- package/dist/files/storage-provider.d.ts +1 -1
- package/dist/files/storage-provider.js +2 -2
- package/dist/gotrue/keys.js +2 -1
- package/dist/realtime/change-checker.js +1 -1
- package/dist/realtime/realtime-app.js +1 -1
- package/dist/realtime/realtime-routes.js +1 -1
- package/dist/realtime/realtime-socket.js +1 -1
- package/dist/rest/caller-settings.js +1 -1
- package/dist/rest/foreign-keys.js +1 -1
- package/dist/rest/parse-select.js +1 -1
- package/dist/rest/rest-error.js +2 -2
- package/dist/rest/rest-routes.js +1 -1
- package/dist/rest/rpc-route.js +1 -1
- package/dist/rest/table-routes.js +2 -2
- package/dist/storage/storage-db.js +1 -1
- package/dist/storage/storage-routes.js +1 -1
- package/dist/supabase-paths.js +11 -0
- package/package.json +4 -4
package/dist/admin-options.d.ts
CHANGED
|
@@ -14,8 +14,8 @@ export type AdminOptions = {
|
|
|
14
14
|
/** Default `/api/v1`. */
|
|
15
15
|
basePath?: string;
|
|
16
16
|
/**
|
|
17
|
-
* The app's publishable and secret keys, with which supabase-js calls the PostgREST-compatible `/rest/v1` and the
|
|
18
|
-
* Realtime-compatible `/realtime/v1` and the Storage-compatible `/storage/v1` (see `ApiKeysOptions`); default `PROTOBASE_PUBLISHABLE_KEY` and
|
|
17
|
+
* The app's publishable and secret keys, with which supabase-js calls the PostgREST-compatible `/api/sb/rest/v1` and the
|
|
18
|
+
* Realtime-compatible `/api/sb/realtime/v1` and the Storage-compatible `/api/sb/storage/v1` (see `ApiKeysOptions`); default `PROTOBASE_PUBLISHABLE_KEY` and
|
|
19
19
|
* `PROTOBASE_SECRET_KEY`. Without either, or with `false`, the app serves neither.
|
|
20
20
|
*/
|
|
21
21
|
apiKeys?: ApiKeysOptions | false;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/api-keys/caller-settings.ts
|
|
2
2
|
/**
|
|
3
|
-
* The transaction settings a request admitted by key (`/rest/v1`, `/storage/v1`) runs with, as Supabase's PostgREST
|
|
3
|
+
* The transaction settings a request admitted by key (`/api/sb/rest/v1`, `/api/sb/storage/v1`) runs with, as Supabase's PostgREST
|
|
4
4
|
* sets them: `role`, which is `SET LOCAL ROLE`, to `anon` or `authenticated`, and the caller's claims in `request.jwt.claims` and its user in
|
|
5
5
|
* `request.jwt.claim.sub`, which `auth.uid()`, `auth.role()` and `auth.jwt()` read. The `role` claim names the
|
|
6
6
|
* database role, as on Supabase, whatever the token's own says. The app's role, of the secret key, sets no role: the
|
package/dist/create-admin.d.ts
CHANGED
|
@@ -72,11 +72,11 @@ export type CreateAdminInput = {
|
|
|
72
72
|
formsDifficulty?: Difficulty;
|
|
73
73
|
/** @internal The users comments name, mention and mail, which tests fake; from `auth` otherwise. */
|
|
74
74
|
commentUsers?: CommentUsers;
|
|
75
|
-
/** @internal Verifies a user's token on `/rest/v1`, `/realtime/v1` and `/storage/v1` and gives its claims, which tests fake; those `/auth/v1` signs otherwise. */
|
|
75
|
+
/** @internal Verifies a user's token on `/api/sb/rest/v1`, `/api/sb/realtime/v1` and `/api/sb/storage/v1` and gives its claims, which tests fake; those `/api/sb/auth/v1` signs otherwise. */
|
|
76
76
|
verifyToken?: (token: string) => Promise<JWTPayload>;
|
|
77
77
|
/**
|
|
78
78
|
* The host's WebSocket upgrade, such as `@hono/node-server`'s, with which an app with keys and live resources serves
|
|
79
|
-
* supabase-js's Realtime at `/realtime/v1/websocket`; without it, the app serves none.
|
|
79
|
+
* supabase-js's Realtime at `/api/sb/realtime/v1/websocket`; without it, the app serves none.
|
|
80
80
|
*/
|
|
81
81
|
upgradeWebSocket?: UpgradeWebSocket;
|
|
82
82
|
};
|
package/dist/create-admin.js
CHANGED
|
@@ -19,6 +19,7 @@ import { realtimeApp } from "./realtime/realtime-app.js";
|
|
|
19
19
|
import { restRoutes } from "./rest/rest-routes.js";
|
|
20
20
|
import { storageRoutes } from "./storage/storage-routes.js";
|
|
21
21
|
import { readAppName } from "./app-name.js";
|
|
22
|
+
import { realtimePath, restPath, storagePath, supabasePath } from "./supabase-paths.js";
|
|
22
23
|
import { goTruePath } from "./gotrue/keys.js";
|
|
23
24
|
import { authBasePath } from "./better-auth/auth-base-path.js";
|
|
24
25
|
import { organizationRoutes } from "./better-auth/organizations/admin-routes.js";
|
|
@@ -237,7 +238,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
|
|
|
237
238
|
skip
|
|
238
239
|
});
|
|
239
240
|
app.use(`${systemPath}/*`, cors);
|
|
240
|
-
if (auth && !"/
|
|
241
|
+
if ((servesKeys || auth) && !"/api/sb".startsWith(`${systemPath}/`)) app.use(`${supabasePath}/*`, cors);
|
|
241
242
|
}
|
|
242
243
|
if (auth) {
|
|
243
244
|
app.route(authBasePath, statusRoute(auth, {
|
|
@@ -288,19 +289,18 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
|
|
|
288
289
|
schema: changes,
|
|
289
290
|
report
|
|
290
291
|
});
|
|
291
|
-
if (appModels.some((model) => model.live)) app.route(systemPath || "/", guarded((live) => void live.route("/", liveRoutes(feed, (session) => visibleNotices(deps, session))), withAnonymous(authenticate, options.roles)));
|
|
292
292
|
if (servesKeys) {
|
|
293
293
|
const verify = verifyToken ?? (auth ? jwtVerifier(goTrueTokens({ auth })) : () => Promise.reject(unauthorized("This app signs no user tokens")));
|
|
294
294
|
const caller = apiKeyCaller({
|
|
295
295
|
keys: apiKeys,
|
|
296
296
|
verify
|
|
297
297
|
});
|
|
298
|
-
app.route(
|
|
298
|
+
app.route(restPath, restRoutes({
|
|
299
299
|
deps,
|
|
300
300
|
caller,
|
|
301
301
|
report
|
|
302
302
|
}));
|
|
303
|
-
if (upgradeWebSocket) app.route(
|
|
303
|
+
if (upgradeWebSocket) app.route(realtimePath, realtimeApp({
|
|
304
304
|
deps,
|
|
305
305
|
feed,
|
|
306
306
|
upgradeWebSocket,
|
|
@@ -317,12 +317,13 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
|
|
|
317
317
|
maxBytes: files.storage.maxBytes,
|
|
318
318
|
caller
|
|
319
319
|
};
|
|
320
|
-
app.route(
|
|
320
|
+
app.route(storagePath, storageRoutes({
|
|
321
321
|
deps: storage,
|
|
322
322
|
report
|
|
323
323
|
}));
|
|
324
324
|
}
|
|
325
325
|
}
|
|
326
|
+
if (appModels.some((model) => model.live)) app.route(systemPath || "/", guarded((live) => void live.route("/", liveRoutes(feed, (session) => visibleNotices(deps, session))), withAnonymous(authenticate, options.roles)));
|
|
326
327
|
app.route(basePath, api);
|
|
327
328
|
app.route(`${deps.systemPath}/meta`, metaApp);
|
|
328
329
|
app.route(systemPath || "/", webManifestRoute(appName, identity, icons));
|
package/dist/files/options.d.ts
CHANGED
|
@@ -23,9 +23,9 @@ export type FilesOptions = {
|
|
|
23
23
|
*/
|
|
24
24
|
trustedHosts?: string[];
|
|
25
25
|
/**
|
|
26
|
-
* Where `supabase.storage` keeps the bytes of its objects, which the app serves at `/storage/v1` when it has an API key
|
|
26
|
+
* Where `supabase.storage` keeps the bytes of its objects, which the app serves at `/api/sb/storage/v1` when it has an API key
|
|
27
27
|
* (see `apiKeys`): `provider` names one of the providers, default `private`, and `maxFileSize` caps every upload, as
|
|
28
|
-
* `50 MB` (the default) or a number of bytes; a bucket's own limit can only be lower. `false` serves no `/storage/v1`.
|
|
28
|
+
* `50 MB` (the default) or a number of bytes; a bucket's own limit can only be lower. `false` serves no `/api/sb/storage/v1`.
|
|
29
29
|
*/
|
|
30
30
|
storage?: {
|
|
31
31
|
provider?: string;
|
package/dist/files/runtime.js
CHANGED
|
@@ -10,7 +10,7 @@ import { parseSize } from "@protobase/schema/internal";
|
|
|
10
10
|
const ticketSeconds = 86400;
|
|
11
11
|
const imageFields = (models) => models.flatMap((model) => Object.values(model.fields).flatMap(({ name, file }) => file && (file.variants || file.original || file.placeholder !== void 0) ? [`${model.name}.${name}`] : []));
|
|
12
12
|
/**
|
|
13
|
-
* The runtime, when a resource has a file field, the config names providers or the app serves `/storage/v1`. It fails at startup when a field's
|
|
13
|
+
* The runtime, when a resource has a file field, the config names providers or the app serves `/api/sb/storage/v1`. It fails at startup when a field's
|
|
14
14
|
* provider is not configured, nothing signs uploads, or a field makes image variants or placeholders without sharp.
|
|
15
15
|
*/
|
|
16
16
|
const createFilesRuntime = ({ options = {}, env, models, systemPath, storage: servesStorage = false }) => {
|
|
@@ -23,7 +23,7 @@ const createFilesRuntime = ({ options = {}, env, models, systemPath, storage: se
|
|
|
23
23
|
const used = [.../* @__PURE__ */ new Set([...fieldProviders, ...storage ? [storage.provider] : []])];
|
|
24
24
|
if (used.length === 0 && !options.providers) return void 0;
|
|
25
25
|
const secret = options.secret ?? env.PROTOBASE_FILES_SECRET ?? env.BETTER_AUTH_SECRET;
|
|
26
|
-
if (!secret) throw new Error("File fields and /storage/v1 need a secret to sign uploads: set BETTER_AUTH_SECRET, PROTOBASE_FILES_SECRET or files.secret");
|
|
26
|
+
if (!secret) throw new Error("File fields and /api/sb/storage/v1 need a secret to sign uploads: set BETTER_AUTH_SECRET, PROTOBASE_FILES_SECRET or files.secret");
|
|
27
27
|
const images = imageFields(models);
|
|
28
28
|
if (images.length > 0 && !options.sharp) throw new Error(`${images.join(", ")} make image variants or placeholders, which need sharp: add it to the project (bun add sharp), then import sharp from 'sharp' in the config and pass files: { sharp }`);
|
|
29
29
|
const providers = resolveProviders(options, env, used);
|
|
@@ -2,7 +2,7 @@ import { FileProvider } from "./store.js";
|
|
|
2
2
|
import { FilesOptions } from "./options.js";
|
|
3
3
|
//#region src/files/storage-provider.d.ts
|
|
4
4
|
/**
|
|
5
|
-
* The provider `/storage/v1` keeps object bytes in, as the app resolves it from the config and the environment, for a
|
|
5
|
+
* The provider `/api/sb/storage/v1` keeps object bytes in, as the app resolves it from the config and the environment, for a
|
|
6
6
|
* command that writes them, such as `protobase import supabase`. Fails when `files.storage` is `false`.
|
|
7
7
|
*/
|
|
8
8
|
export declare const storageProvider: (options: FilesOptions, env: Record<string, string | undefined>) => {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { resolveProviders } from "./providers.js";
|
|
2
2
|
//#region src/files/storage-provider.ts
|
|
3
|
-
/** The provider `/storage/v1` keeps object bytes in: `files.storage.provider`, default `private`. */
|
|
3
|
+
/** The provider `/api/sb/storage/v1` keeps object bytes in: `files.storage.provider`, default `private`. */
|
|
4
4
|
const storageProviderName = (options) => options.storage === false ? void 0 : options.storage?.provider ?? "private";
|
|
5
5
|
/**
|
|
6
|
-
* The provider `/storage/v1` keeps object bytes in, as the app resolves it from the config and the environment, for a
|
|
6
|
+
* The provider `/api/sb/storage/v1` keeps object bytes in, as the app resolves it from the config and the environment, for a
|
|
7
7
|
* command that writes them, such as `protobase import supabase`. Fails when `files.storage` is `false`.
|
|
8
8
|
*/
|
|
9
9
|
const storageProvider = (options, env) => {
|
package/dist/gotrue/keys.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { supabasePath } from "../supabase-paths.js";
|
|
1
2
|
//#region src/gotrue/keys.ts
|
|
2
3
|
/** Where the GoTrue-compatible endpoints are served, below the app's origin. */
|
|
3
|
-
const goTruePath =
|
|
4
|
+
const goTruePath = `${supabasePath}/auth/v1`;
|
|
4
5
|
/** The key pair the JWT plugin keeps beside its EdDSA one for these tokens; it makes it on the first token. */
|
|
5
6
|
const goTrueKeyPair = { alg: "ES256" };
|
|
6
7
|
const goTrueAlgorithm = goTrueKeyPair.alg;
|
|
@@ -24,7 +24,7 @@ const seen = (entry, filters, rows) => new Map(rows.map((row) => [encodeKey(keyC
|
|
|
24
24
|
}]));
|
|
25
25
|
/**
|
|
26
26
|
* Checks which changes callers may see, for every group in one read-only transaction: per group it sets the role and
|
|
27
|
-
* claims as `/rest/v1` does, then reads the current rows by key, so the table's RLS policies decide, and evaluates
|
|
27
|
+
* claims as `/api/sb/rest/v1` does, then reads the current rows by key, so the table's RLS policies decide, and evaluates
|
|
28
28
|
* each filter in Postgres, as the column's type compares. The transaction holds one pooled connection only while it
|
|
29
29
|
* runs. A group whose check fails is checked again in a transaction of its own, so it costs only its own callers the
|
|
30
30
|
* batch.
|
|
@@ -3,7 +3,7 @@ import { changeHub } from "./change-hub.js";
|
|
|
3
3
|
import { realtimeRoutes } from "./realtime-routes.js";
|
|
4
4
|
//#region src/realtime/realtime-app.ts
|
|
5
5
|
/**
|
|
6
|
-
* `/realtime/v1`: the `postgres_changes` of the live resources, each checked under the subscriber's role, claims and
|
|
6
|
+
* `/api/sb/realtime/v1`: the `postgres_changes` of the live resources, each checked under the subscriber's role, claims and
|
|
7
7
|
* the tables' RLS policies before it is sent, and broadcasts and presence between the channels of the process.
|
|
8
8
|
*/
|
|
9
9
|
const realtimeApp = ({ deps, feed, upgradeWebSocket, caller, report }) => {
|
|
@@ -10,7 +10,7 @@ const keyRequest = (apikey, token) => new Request("http://realtime.invalid/", {
|
|
|
10
10
|
...token !== void 0 && { authorization: `Bearer ${token}` }
|
|
11
11
|
} });
|
|
12
12
|
/**
|
|
13
|
-
* `GET /realtime/v1/websocket?apikey=…&vsn=…`: Supabase Realtime's socket, for `postgres_changes` of the live
|
|
13
|
+
* `GET /api/sb/realtime/v1/websocket?apikey=…&vsn=…`: Supabase Realtime's socket, for `postgres_changes` of the live
|
|
14
14
|
* resources, and broadcasts and presence between the sockets of this process (see `realtimeSocket`). A missing or wrong
|
|
15
15
|
* key is refused before the upgrade, with 401.
|
|
16
16
|
*/
|
|
@@ -11,7 +11,7 @@ const refusal = (error) => {
|
|
|
11
11
|
throw error;
|
|
12
12
|
};
|
|
13
13
|
/**
|
|
14
|
-
* One WebSocket of `/realtime/v1`, speaking Phoenix channels as realtime-js expects: heartbeats, channel joins with
|
|
14
|
+
* One WebSocket of `/api/sb/realtime/v1`, speaking Phoenix channels as realtime-js expects: heartbeats, channel joins with
|
|
15
15
|
* `postgres_changes` bindings and an access token, token refreshes on an open channel, broadcasts with their `self`
|
|
16
16
|
* and `ack` options, presence tracking, and leaves. A channel's token ends it when it expires, unless a newer one came
|
|
17
17
|
* first. Messages are handled one at a time, in order. Private channels are refused for now.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/rest/caller-settings.ts
|
|
2
2
|
/**
|
|
3
|
-
* The transaction settings a `/rest/v1` request runs with, as Supabase's PostgREST sets them: `role`, which is
|
|
3
|
+
* The transaction settings a `/api/sb/rest/v1` request runs with, as Supabase's PostgREST sets them: `role`, which is
|
|
4
4
|
* `SET LOCAL ROLE`, to `anon` or `authenticated`, and the caller's claims in `request.jwt.claims` and its user in
|
|
5
5
|
* `request.jwt.claim.sub`, which `auth.uid()`, `auth.role()` and `auth.jwt()` read. The `role` claim names the
|
|
6
6
|
* database role, as on Supabase, whatever the token's own says. The app's role, of the secret key, sets no role: the
|
|
@@ -15,7 +15,7 @@ const readForeignKeys = async (db) => {
|
|
|
15
15
|
return rows;
|
|
16
16
|
};
|
|
17
17
|
/**
|
|
18
|
-
* The foreign keys of the `public` schema, which `/rest/v1` embeds through, read from the catalog once and kept for the
|
|
18
|
+
* The foreign keys of the `public` schema, which `/api/sb/rest/v1` embeds through, read from the catalog once and kept for the
|
|
19
19
|
* process lifetime, as the scan guard keeps indexes: migrations run before a new version serves.
|
|
20
20
|
*/
|
|
21
21
|
const foreignKeyCache = (db) => {
|
|
@@ -7,7 +7,7 @@ const embedPattern = /^(?:([^:(!]+):(?!:))?([^!(]+)((?:![^!(]+)*)\(([\s\S]*)\)$/
|
|
|
7
7
|
const parseEmbed = (item) => {
|
|
8
8
|
const [, alias, nameText = "", modifiers = "", inner = ""] = embedPattern.exec(item) ?? [];
|
|
9
9
|
const name = nameText.trim();
|
|
10
|
-
if (!name || name.startsWith("...") || name.includes(".") || name === "count") throw restError(400, "PGRST100", `"${item}" spreads a resource or calls an aggregate, which /rest/v1 does not support yet`);
|
|
10
|
+
if (!name || name.startsWith("...") || name.includes(".") || name === "count") throw restError(400, "PGRST100", `"${item}" spreads a resource or calls an aggregate, which /api/sb/rest/v1 does not support yet`);
|
|
11
11
|
const flags = modifiers.split("!").slice(1).map((flag) => flag.trim());
|
|
12
12
|
const hint = flags.find((flag) => flag !== "inner" && flag !== "left");
|
|
13
13
|
return {
|
package/dist/rest/rest-error.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isDatabaseError } from "../error-handler.js";
|
|
2
2
|
//#region src/rest/rest-error.ts
|
|
3
|
-
/** A `/rest/v1` error: its status, the body, and headers such as `Content-Range`. Thrown anywhere below the route. */
|
|
3
|
+
/** A `/api/sb/rest/v1` error: its status, the body, and headers such as `Content-Range`. Thrown anywhere below the route. */
|
|
4
4
|
var RestError = class RestError extends Error {
|
|
5
5
|
status;
|
|
6
6
|
body;
|
|
@@ -66,7 +66,7 @@ const databaseStatus = (code, message, anonymous) => {
|
|
|
66
66
|
return codeStatus[code] ?? prefixStatus.find(([prefix]) => code.startsWith(prefix))?.[1] ?? 400;
|
|
67
67
|
};
|
|
68
68
|
/**
|
|
69
|
-
* The `/rest/v1` error an error stands for: its own, or a database error with PostgREST's status and the error's
|
|
69
|
+
* The `/api/sb/rest/v1` error an error stands for: its own, or a database error with PostgREST's status and the error's
|
|
70
70
|
* message, detail and hint, which PostgREST passes on as they are. Other errors give `undefined`. Refusing a write by
|
|
71
71
|
* RLS is 401 for an anonymous caller and 403 for others, as on Supabase.
|
|
72
72
|
*/
|
package/dist/rest/rest-routes.js
CHANGED
|
@@ -7,7 +7,7 @@ import { tableRoutes } from "./table-routes.js";
|
|
|
7
7
|
import { Hono } from "hono";
|
|
8
8
|
//#region src/rest/rest-routes.ts
|
|
9
9
|
/**
|
|
10
|
-
* The PostgREST-compatible API supabase-js's `from()` and `rpc()` call, mounted at `/rest/v1`. Each request runs in one
|
|
10
|
+
* The PostgREST-compatible API supabase-js's `from()` and `rpc()` call, mounted at `/api/sb/rest/v1`. Each request runs in one
|
|
11
11
|
* transaction as `anon`, `authenticated` or the app's own role, so the tables' RLS policies decide what it may read and
|
|
12
12
|
* write. Errors come back as PostgREST's `{ code, details, hint, message }`; an unexpected one as a 500 that names
|
|
13
13
|
* nothing of it.
|
package/dist/rest/rpc-route.js
CHANGED
|
@@ -38,7 +38,7 @@ const call = async (deps, c) => {
|
|
|
38
38
|
const source = callSource(chosen, args);
|
|
39
39
|
const query = parseQuery(params, c.req.header("range") ?? null, new Set(given));
|
|
40
40
|
const shape = query.select.map((item) => {
|
|
41
|
-
if (item.kind === "embed") throw restError(400, "PGRST100", `"${item.name}" embeds a resource in a function's rows, which /rest/v1 does not support yet`);
|
|
41
|
+
if (item.kind === "embed") throw restError(400, "PGRST100", `"${item.name}" embeds a resource in a function's rows, which /api/sb/rest/v1 does not support yet`);
|
|
42
42
|
return item;
|
|
43
43
|
});
|
|
44
44
|
if (chosen.info.returns === "void") {
|
|
@@ -13,7 +13,7 @@ import { Hono } from "hono";
|
|
|
13
13
|
import { sql } from "kysely";
|
|
14
14
|
//#region src/rest/table-routes.ts
|
|
15
15
|
/**
|
|
16
|
-
* The resource whose table `name` is, in the `public` schema or the search path's: `/rest/v1` serves the tables the app
|
|
16
|
+
* The resource whose table `name` is, in the `public` schema or the search path's: `/api/sb/rest/v1` serves the tables the app
|
|
17
17
|
* declares as resources, as PostgREST serves those of its exposed schema. Any other is 404 (PGRST205).
|
|
18
18
|
*/
|
|
19
19
|
const servedEntry = (deps, name) => deps.registry.entries.find((one) => one.model.table.name === name && (one.model.table.schema ?? "public") === "public");
|
|
@@ -181,7 +181,7 @@ const remove = async (deps, foreignKeys, c) => {
|
|
|
181
181
|
return withHeaders(writeResponse(written, object, 200, 204, `*/${counted(prefer, written.total) ?? "*"}`), warning);
|
|
182
182
|
};
|
|
183
183
|
/**
|
|
184
|
-
* `/rest/v1/<table>`: read with GET and HEAD, insert and upsert with POST, update with PATCH, delete with DELETE. Reads
|
|
184
|
+
* `/api/sb/rest/v1/<table>`: read with GET and HEAD, insert and upsert with POST, update with PATCH, delete with DELETE. Reads
|
|
185
185
|
* and the rows a write returns embed related rows through `foreignKeys`.
|
|
186
186
|
*/
|
|
187
187
|
const tableRoutes = (deps, foreignKeys) => {
|
|
@@ -3,7 +3,7 @@ import { callerSettings } from "../api-keys/caller-settings.js";
|
|
|
3
3
|
import { sql } from "kysely";
|
|
4
4
|
//#region src/storage/storage-db.ts
|
|
5
5
|
/**
|
|
6
|
-
* Makes the rest of the transaction run as `caller`: its role and claims, as `/rest/v1` sets them (see
|
|
6
|
+
* Makes the rest of the transaction run as `caller`: its role and claims, as `/api/sb/rest/v1` sets them (see
|
|
7
7
|
* `callerSettings`), so the restored `storage.objects` and `storage.buckets` policies decide what it reads and writes.
|
|
8
8
|
* What runs before it runs as the app's own role, which owns the tables.
|
|
9
9
|
*/
|
|
@@ -5,7 +5,7 @@ import { signedRoutes } from "./signed-routes.js";
|
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
//#region src/storage/storage-routes.ts
|
|
7
7
|
/**
|
|
8
|
-
* The Storage-compatible API `supabase.storage` calls, mounted at `/storage/v1`, over the app's storage provider. Every
|
|
8
|
+
* The Storage-compatible API `supabase.storage` calls, mounted at `/api/sb/storage/v1`, over the app's storage provider. Every
|
|
9
9
|
* read and write of `storage.objects` and `storage.buckets` runs as the request's caller (`anon`, `authenticated` or
|
|
10
10
|
* the app's own role), so the policies restored with them decide; public buckets and signed URLs need no key. Errors
|
|
11
11
|
* come back as Supabase Storage's `{ statusCode, error, message }`; an unexpected one as a 500 that names nothing of it.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/supabase-paths.ts
|
|
2
|
+
/** Where the Supabase-compatible endpoints live, below the app's origin: supabase-js takes `<origin>/api/sb` as its URL. */
|
|
3
|
+
const supabasePath = "/api/sb";
|
|
4
|
+
/** The PostgREST-compatible API of `from()` and `rpc()`. */
|
|
5
|
+
const restPath = `${supabasePath}/rest/v1`;
|
|
6
|
+
/** The Realtime-compatible socket of `channel()`. */
|
|
7
|
+
const realtimePath = `${supabasePath}/realtime/v1`;
|
|
8
|
+
/** The Storage-compatible API of `storage`. */
|
|
9
|
+
const storagePath = `${supabasePath}/storage/v1`;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { realtimePath, restPath, storagePath, supabasePath };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "The Protobase REST API as a Hono app, with Better Auth sign-in.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@better-auth/passkey": "^1.7.7",
|
|
34
34
|
"@better-auth/sso": "^1.7.7",
|
|
35
|
-
"@protobase/layout": "0.
|
|
36
|
-
"@protobase/query": "0.
|
|
37
|
-
"@protobase/schema": "0.
|
|
35
|
+
"@protobase/layout": "0.31.0",
|
|
36
|
+
"@protobase/query": "0.31.0",
|
|
37
|
+
"@protobase/schema": "0.31.0",
|
|
38
38
|
"@react-email/render": "^2.1.0",
|
|
39
39
|
"@scalar/hono-api-reference": "^0.12.10",
|
|
40
40
|
"@sentry/core": "^11.7.0",
|