@protobase/cli 0.66.7 → 0.66.9
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/cli/package.js +1 -1
- package/dist/dev/api-forward.js +3 -2
- package/dist/dev/app-files.js +11 -0
- package/dist/dev/check-remote-api.js +18 -0
- package/dist/dev/entry.js +1 -1
- package/dist/dev/remote-api.js +58 -0
- package/dist/dev/run-ui.js +34 -0
- package/dist/dev/run.js +5 -8
- package/dist/main.js +22 -10
- package/package.json +7 -7
- package/skills/protobase/references/features/ai.md +10 -7
- package/skills/protobase/references/features/build-and-run.md +2 -0
- package/skills/protobase/references/features/compatibility.md +6 -6
- package/skills/protobase/references/features/data-model.md +1 -1
- package/skills/protobase/references/features/email-and-public-forms.md +2 -0
- package/skills/protobase/references/features/events-webhooks-and-jobs.md +2 -0
- package/skills/protobase/references/features/files-and-images.md +1 -0
- package/skills/protobase/references/features/look-and-language.md +1 -0
- package/skills/protobase/references/features/navigation.md +5 -4
- package/skills/protobase/references/features/sign-in-and-accounts.md +6 -0
- package/skills/protobase/references/features/your-own-code.md +4 -2
- package/skills/protobase/references/getting-started.md +8 -2
- package/skills/protobase/references/guides/api-functions.md +2 -2
- package/skills/protobase/references/reference/api.md +1 -1
- package/skills/protobase/references/reference/app-identity.md +1 -1
- package/skills/protobase/references/reference/assistant.md +22 -2
- package/skills/protobase/references/reference/auth.md +33 -6
- package/skills/protobase/references/reference/cli.md +19 -3
- package/skills/protobase/references/reference/client.md +1 -1
- package/skills/protobase/references/reference/comments.md +1 -1
- package/skills/protobase/references/reference/custom-components.md +7 -1
- package/skills/protobase/references/reference/data-config.md +1 -1
- package/skills/protobase/references/reference/email.md +1 -1
- package/skills/protobase/references/reference/files.md +4 -0
- package/skills/protobase/references/reference/jobs.md +3 -1
- package/skills/protobase/references/reference/languages.md +2 -2
- package/skills/protobase/references/reference/mcp.md +21 -1
- package/skills/protobase/references/reference/plugins.md +6 -4
- package/skills/protobase/references/reference/sidebar.md +6 -0
- package/skills/protobase/references/reference/supabase-realtime.md +5 -1
- package/skills/protobase/references/reference/supabase-rest.md +5 -1
- package/skills/protobase/references/reference/supabase-storage.md +5 -1
- package/skills/protobase/references/reference/theme.md +1 -1
- package/skills/protobase/references/reference/ui-config.md +6 -2
- package/skills/protobase/references/reference/versioning.md +2 -0
package/dist/cli/package.js
CHANGED
package/dist/dev/api-forward.js
CHANGED
|
@@ -9,7 +9,8 @@ import { request } from "node:http";
|
|
|
9
9
|
* discovery documents MCP clients read (`oauthWellKnownPaths`), below which a resource's own one is, and the push worker
|
|
10
10
|
* (`withDevServiceWorker`). Vite serves the rest.
|
|
11
11
|
*/
|
|
12
|
-
const
|
|
12
|
+
const apiRequestPattern = /^(\/api(\/|\?|$)|\/\.well-known\/webauthn(\?|$)|\/\.well-known\/oauth-(authorization-server|protected-resource)(\/|\?|$)|\/protobase\.sw\.js(\?|$))/;
|
|
13
|
+
const isApiRequest = (url) => apiRequestPattern.test(url);
|
|
13
14
|
const hopByHop = /* @__PURE__ */ new Set([
|
|
14
15
|
"connection",
|
|
15
16
|
"keep-alive",
|
|
@@ -67,4 +68,4 @@ const apiForward = (port) => ({
|
|
|
67
68
|
}
|
|
68
69
|
});
|
|
69
70
|
//#endregion
|
|
70
|
-
export { apiForward, isApiRequest };
|
|
71
|
+
export { apiForward, apiRequestPattern, isApiRequest };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { adminAppMain } from "../project/admin-app.js";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
//#region src/dev/app-files.ts
|
|
5
|
+
/** Fails, naming them, when the admin app in `appDir` lacks the files `protobase dev` serves it from. */
|
|
6
|
+
const requireAppFiles = (appDir) => {
|
|
7
|
+
const missing = ["index.html", adminAppMain].filter((file) => !existsSync(path.join(appDir, file)));
|
|
8
|
+
if (missing.length > 0) throw new Error(`The admin app is missing ${missing.join(", ")} in ${appDir}; \`protobase dev\` serves the app from there`);
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
export { requireAppFiles };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/dev/check-remote-api.ts
|
|
2
|
+
/**
|
|
3
|
+
* Fails, before the dev server starts, when the instance at `origin` cannot be reached or does not answer `/api/meta`
|
|
4
|
+
* as a Protobase API does, with the X-Meta-Version header.
|
|
5
|
+
*/
|
|
6
|
+
const checkRemoteApi = async (origin) => {
|
|
7
|
+
const meta = `${origin}/api/meta`;
|
|
8
|
+
const answer = await fetch(meta, {
|
|
9
|
+
signal: AbortSignal.timeout(1e4),
|
|
10
|
+
redirect: "manual"
|
|
11
|
+
}).catch((error) => {
|
|
12
|
+
throw new Error(`Cannot reach the API at ${meta}: ${error.cause?.message ?? error.message}`);
|
|
13
|
+
});
|
|
14
|
+
await answer.body?.cancel();
|
|
15
|
+
if (!answer.headers.has("x-meta-version")) throw new Error(`${origin} answered ${meta} with ${answer.status} and no X-Meta-Version header; is it a Protobase app?`);
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
export { checkRemoteApi };
|
package/dist/dev/entry.js
CHANGED
|
@@ -55,7 +55,7 @@ const ui = {
|
|
|
55
55
|
icons: projectIcons(projectDir)
|
|
56
56
|
};
|
|
57
57
|
const { admin, jobs } = projectAdmin(project, db, ui, reporter, upgradeWebSocket);
|
|
58
|
-
await devJobs(db, jobs, admin.decryptField, project.mailer);
|
|
58
|
+
if (process.env.PROTOBASE_DEV_JOBS !== "false") await devJobs(db, jobs, admin.decryptField, project.mailer);
|
|
59
59
|
devRealtimePrune(admin);
|
|
60
60
|
var entry_default = withDevServiceWorker(new Hono(), Boolean(project.notifications)).route("/", admin);
|
|
61
61
|
/** Whether the auth store has a user yet, for the banner's sign-in line; undefined without one. */
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { isAllowedHost } from "./allowed-host.js";
|
|
2
|
+
import { apiRequestPattern } from "./api-forward.js";
|
|
3
|
+
//#region src/dev/remote-api.ts
|
|
4
|
+
/**
|
|
5
|
+
* The origin of the Protobase instance `protobase dev --api` hands the API to, from the URL given: http or https, and
|
|
6
|
+
* no path, since an instance serves its API at /api of its origin.
|
|
7
|
+
*/
|
|
8
|
+
const remoteApiOrigin = (value) => {
|
|
9
|
+
const url = URL.parse(value);
|
|
10
|
+
if (!url || url.protocol !== "http:" && url.protocol !== "https:") throw new Error(`--api expects an http or https URL, such as https://app.example.com, not "${value}"`);
|
|
11
|
+
if (url.pathname !== "/" || url.search) throw new Error(`--api expects the instance's origin, such as ${url.origin}, without a path`);
|
|
12
|
+
return url.origin;
|
|
13
|
+
};
|
|
14
|
+
const ofDevServer = (value, request) => {
|
|
15
|
+
const url = value === void 0 ? null : URL.parse(value);
|
|
16
|
+
return url?.host === request.headers.host ? url : void 0;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Hands the requests the API process answers in `protobase dev` (`isApiRequest`), and the API's WebSockets, to the
|
|
20
|
+
* instance at `origin` through Vite's proxy. The instance sees its own Host, and its own origin in the Origin and Referer
|
|
21
|
+
* of a page of this dev server, so its sign-in and its checks of a request's origin take them; a request from any other
|
|
22
|
+
* page keeps the origin it came with. Its cookies come back without their Domain, for localhost to keep, and a
|
|
23
|
+
* redirect to its own address comes back as a path on this server. A socket whose Host fails Vite's host check is
|
|
24
|
+
* refused, as Vite refuses such a request.
|
|
25
|
+
*/
|
|
26
|
+
const remoteApi = (origin) => {
|
|
27
|
+
let allowedHosts = [];
|
|
28
|
+
return {
|
|
29
|
+
name: "protobase-remote-api",
|
|
30
|
+
config: () => ({ server: { proxy: { [apiRequestPattern.source]: {
|
|
31
|
+
target: origin,
|
|
32
|
+
changeOrigin: true,
|
|
33
|
+
ws: true,
|
|
34
|
+
rewriteWsOrigin: true,
|
|
35
|
+
cookieDomainRewrite: { "*": "" },
|
|
36
|
+
bypass: (request, response) => {
|
|
37
|
+
if (!(response === void 0)) return void 0;
|
|
38
|
+
return isAllowedHost(request.headers.host, allowedHosts) ? void 0 : false;
|
|
39
|
+
},
|
|
40
|
+
configure: (proxy) => {
|
|
41
|
+
proxy.on("proxyReq", (upstream, request) => {
|
|
42
|
+
if (ofDevServer(request.headers.origin, request)) upstream.setHeader("origin", origin);
|
|
43
|
+
const referer = ofDevServer(request.headers.referer, request);
|
|
44
|
+
if (referer) upstream.setHeader("referer", `${origin}${referer.pathname}${referer.search}`);
|
|
45
|
+
});
|
|
46
|
+
proxy.on("proxyRes", (answer) => {
|
|
47
|
+
const location = answer.headers.location;
|
|
48
|
+
if (location?.startsWith(`${origin}/`)) answer.headers.location = location.slice(origin.length);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
} } } }),
|
|
52
|
+
configResolved: (config) => {
|
|
53
|
+
allowedHosts = config.server.allowedHosts;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
//#endregion
|
|
58
|
+
export { remoteApi, remoteApiOrigin };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { adminAppDir } from "../project/admin-app.js";
|
|
2
|
+
import { alsoOnIpv6, checkPortFree } from "./loopback.js";
|
|
3
|
+
import { remoteApi } from "./remote-api.js";
|
|
4
|
+
import { requireAppFiles } from "./app-files.js";
|
|
5
|
+
import { checkRemoteApi } from "./check-remote-api.js";
|
|
6
|
+
//#region src/dev/run-ui.ts
|
|
7
|
+
/**
|
|
8
|
+
* `protobase dev --api`: the admin app with the project's UI, theme and icons, as `runDev` serves it, with its API
|
|
9
|
+
* requests handed to a running instance instead of an API process of its own; no database and no project config are read.
|
|
10
|
+
*/
|
|
11
|
+
const runUiDev = async (options, out) => {
|
|
12
|
+
const appDir = options.appDir ?? adminAppDir;
|
|
13
|
+
requireAppFiles(appDir);
|
|
14
|
+
await checkRemoteApi(options.api);
|
|
15
|
+
if (options.port !== 0) await checkPortFree(options.port);
|
|
16
|
+
const { createAppServer } = await import("./app-server.js");
|
|
17
|
+
const server = await createAppServer({
|
|
18
|
+
...options,
|
|
19
|
+
appDir,
|
|
20
|
+
extensions: options.extensions ?? []
|
|
21
|
+
}, [remoteApi(options.api)]);
|
|
22
|
+
const closeAndThrow = async (error) => {
|
|
23
|
+
await server.close();
|
|
24
|
+
throw error;
|
|
25
|
+
};
|
|
26
|
+
await server.listen().catch(closeAndThrow);
|
|
27
|
+
const address = server.httpServer?.address();
|
|
28
|
+
const port = typeof address === "object" && address ? address.port : options.port;
|
|
29
|
+
if (server.httpServer) await alsoOnIpv6(server.httpServer, port).catch(closeAndThrow);
|
|
30
|
+
out(`protobase dev ready at http://localhost:${port}\n project ${options.projectDir} (UI only)\n api ${options.api}/api/v1, through http://localhost:${port}/api/v1\n login sign in as a user of ${options.api}\n`);
|
|
31
|
+
return server;
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
34
|
+
export { runUiDev };
|
package/dist/dev/run.js
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import { loadNearestEnvFile } from "./env.js";
|
|
2
|
-
import { adminAppDir
|
|
2
|
+
import { adminAppDir } from "../project/admin-app.js";
|
|
3
3
|
import { alsoOnIpv6, checkPortFree } from "./loopback.js";
|
|
4
|
+
import { apiForward } from "./api-forward.js";
|
|
5
|
+
import { requireAppFiles } from "./app-files.js";
|
|
4
6
|
import { checkDatabase } from "./check-database.js";
|
|
5
7
|
import { authDriftLines } from "./auth-drift-lines.js";
|
|
6
8
|
import { loginLine } from "./login-line.js";
|
|
7
|
-
import { apiForward } from "./api-forward.js";
|
|
8
9
|
import { forkApiChild } from "./api-child.js";
|
|
9
|
-
import { existsSync } from "node:fs";
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
//#region src/dev/run.ts
|
|
12
|
-
const requireAppFiles = (appDir) => {
|
|
13
|
-
const missing = ["index.html", adminAppMain].filter((file) => !existsSync(path.join(appDir, file)));
|
|
14
|
-
if (missing.length > 0) throw new Error(`The admin app is missing ${missing.join(", ")} in ${appDir}; \`protobase dev\` serves the app from there`);
|
|
15
|
-
};
|
|
16
12
|
const runDev = async (options, out) => {
|
|
17
13
|
loadNearestEnvFile(options.projectDir);
|
|
18
14
|
const url = process.env[options.envVar];
|
|
@@ -25,6 +21,7 @@ const runDev = async (options, out) => {
|
|
|
25
21
|
process.env.PROTOBASE_PROJECT = options.projectDir;
|
|
26
22
|
process.env.PROTOBASE_EXTENSIONS = JSON.stringify(extensions);
|
|
27
23
|
process.env.PROTOBASE_DATABASE_URL = url;
|
|
24
|
+
process.env.PROTOBASE_DEV_JOBS = String(options.jobs ?? true);
|
|
28
25
|
const onApiExit = (code) => {
|
|
29
26
|
process.stderr.write(`error: the API process of protobase dev ended with exit code ${code}\n`);
|
|
30
27
|
server.close().then(() => process.exit(code || 1));
|
|
@@ -63,7 +60,7 @@ const runDev = async (options, out) => {
|
|
|
63
60
|
const started = await apiChild.start(`http://localhost:${port}`).catch(closeAndThrow);
|
|
64
61
|
apiReady(started);
|
|
65
62
|
const { anyUsers, authDrift } = started;
|
|
66
|
-
out(`protobase dev ready at http://localhost:${port}\n project ${options.projectDir}\n api http://localhost:${port}/api/v1 (docs at /api/docs)\n` + loginLine(anyUsers, process.env.npm_config_user_agent) + authDriftLines(authDrift));
|
|
63
|
+
out(`protobase dev ready at http://localhost:${port}\n project ${options.projectDir}\n api http://localhost:${port}/api/v1 (docs at /api/docs)\n` + (options.jobs === false ? " jobs off: no jobs run and no events are delivered (--no-jobs)\n" : "") + loginLine(anyUsers, process.env.npm_config_user_agent) + authDriftLines(authDrift));
|
|
67
64
|
return server;
|
|
68
65
|
};
|
|
69
66
|
//#endregion
|
package/dist/main.js
CHANGED
|
@@ -60,22 +60,34 @@ program.command("doctor").description("Check a config folder against the databas
|
|
|
60
60
|
}, out);
|
|
61
61
|
if (errors > 0) process.exitCode = 1;
|
|
62
62
|
});
|
|
63
|
-
|
|
64
|
-
const [{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
const runUiDevAgainst = async (api, options) => {
|
|
64
|
+
const [{ runUiDev }, { remoteApiOrigin }] = await Promise.all([import("./dev/run-ui.js"), import("./dev/remote-api.js")]);
|
|
65
|
+
return runUiDev({
|
|
66
|
+
...options,
|
|
67
|
+
api: remoteApiOrigin(api)
|
|
68
|
+
}, out);
|
|
69
|
+
};
|
|
70
|
+
const runFullDev = async (envVar, jobs, options) => {
|
|
71
|
+
const { runDev } = await import("./dev/run.js");
|
|
70
72
|
const server = await runDev({
|
|
73
|
+
...options,
|
|
74
|
+
envVar,
|
|
75
|
+
jobs
|
|
76
|
+
}, out);
|
|
77
|
+
warn(placeholderSecretWarning(process.env));
|
|
78
|
+
return server;
|
|
79
|
+
};
|
|
80
|
+
program.command("dev").description("Serve the admin app and API for the project in the current directory").option("--port <port>", "port for the app and API", "5173").option("--env <variable>", "environment variable holding the database URL", "DATABASE_URL").option("--allowed-hosts <hosts>", "extra Host headers to accept, comma separated; a leading dot matches subdomains (.trycloudflare.com)").option("--cache-dir <dir>", "Vite's dependency cache, so parallel dev servers do not share one").option("--extend <dir>", "merge the protobase.config.ts and protobase.ui.tsx in this folder before the project's, repeatable", collect, []).option("--no-jobs", "run none of the app's jobs and deliver none of its events, such as against its hosted database").option("--api <url>", "serve the admin app alone, with the API of the running Protobase instance at this URL; no database or API process").action(async (opts) => {
|
|
81
|
+
const [{ resolveExtensions }, { writeProjectTypes }] = await Promise.all([import("./project/extensions.js"), import("./project/project-types.js")]);
|
|
82
|
+
await writeProjectTypes(process.cwd());
|
|
83
|
+
const shared = {
|
|
71
84
|
extensions: resolveExtensions(opts.extend),
|
|
72
85
|
port: Number(opts.port),
|
|
73
|
-
envVar: opts.env,
|
|
74
86
|
projectDir: process.cwd(),
|
|
75
87
|
cacheDir: opts.cacheDir && path.resolve(opts.cacheDir),
|
|
76
88
|
allowedHosts: parseHosts(opts.allowedHosts)
|
|
77
|
-
}
|
|
78
|
-
|
|
89
|
+
};
|
|
90
|
+
const server = opts.api ? await runUiDevAgainst(opts.api, shared) : await runFullDev(opts.env, opts.jobs, shared);
|
|
79
91
|
process.once("SIGINT", () => void server.close().then(() => process.exit(0)));
|
|
80
92
|
process.once("SIGTERM", () => void server.close().then(() => process.exit(0)));
|
|
81
93
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/cli",
|
|
3
|
-
"version": "0.66.
|
|
3
|
+
"version": "0.66.9",
|
|
4
4
|
"description": "The protobase command: scaffold, doctor, dev, build and serve a Protobase project.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -43,12 +43,12 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@better-auth/core": "^1.7.12",
|
|
45
45
|
"@hono/node-server": "^2.1.3",
|
|
46
|
-
"@protobase/config": "0.66.
|
|
47
|
-
"@protobase/layout": "0.66.
|
|
48
|
-
"@protobase/query": "0.66.
|
|
49
|
-
"@protobase/schema": "0.66.
|
|
50
|
-
"@protobase/server": "0.66.
|
|
51
|
-
"@protobase/ui": "0.66.
|
|
46
|
+
"@protobase/config": "0.66.9",
|
|
47
|
+
"@protobase/layout": "0.66.9",
|
|
48
|
+
"@protobase/query": "0.66.9",
|
|
49
|
+
"@protobase/schema": "0.66.9",
|
|
50
|
+
"@protobase/server": "0.66.9",
|
|
51
|
+
"@protobase/ui": "0.66.9",
|
|
52
52
|
"@react-email/render": "^2.1.0",
|
|
53
53
|
"@scalar/hono-api-reference": "^0.12.10",
|
|
54
54
|
"@sentry/vite-plugin": "^11.10.0",
|
|
@@ -6,16 +6,19 @@ Fields and actions filled by a model, the assistant beside the admin, and MCP.
|
|
|
6
6
|
|
|
7
7
|
- [AI fields](../reference/ai.md#ai-fields): A model fills a field from a prompt that uses the record's other fields, after each change.
|
|
8
8
|
- [AI actions](../reference/ai.md#ai-actions): A button asks a model to write a field now, such as a suggested priority or a translation.
|
|
9
|
-
- [Assistant](../reference/assistant.md): A chat beside the admin that answers questions about the app, reads its data and proposes changes for approval.
|
|
9
|
+
- [Assistant](../reference/assistant.md) (beta): A chat beside the admin that answers questions about the app, reads its data and proposes changes for approval.
|
|
10
10
|
- [Choice of model](../reference/assistant.md#which-backend): The assistant runs on OpenRouter or any OpenAI-compatible endpoint, also a local one.
|
|
11
|
-
- [Assistant tools](../reference/assistant.md#the-apps-own-tools): Your own tools for the assistant, acting on records as the user.
|
|
12
|
-
- [
|
|
11
|
+
- [Assistant tools](../reference/assistant.md#the-apps-own-tools) (beta): Your own tools for the assistant, acting on records as the user.
|
|
12
|
+
- [Assistant widgets](../reference/assistant.md#widget-parts): Your own React components in the chat, showing a record as it is now and taking decisions on it, from last month's chat too.
|
|
13
|
+
- [Assistant backend of your own](../reference/assistant.md#another-backend): The chat beside the admin talks to a backend elsewhere, with its own prompt and tools, built from the same pieces as the built-in one.
|
|
14
|
+
- [MCP server](../reference/mcp.md#the-tools) (beta): AI clients connect to the app over MCP as the signed-in person: they list the resources in a small first call, describe one resource's fields when they need them, and find and read records within that person's access, soft-deleted ones too when asked.
|
|
13
15
|
- [MCP read fields](../reference/mcp.md#reading-only-what-is-needed): AI clients read only the fields they name, a plain list returns the columns of the app's table, and the server tells clients how to keep reads small.
|
|
14
|
-
- [MCP counts](../reference/mcp.md#the-tools): AI clients count the records a filter finds, in all or per value of a field such as a status, without reading the records.
|
|
15
|
-
- [MCP app icon](../reference/mcp.md#the-apps-icon): AI clients show the app's own icon beside its name when someone adds its MCP endpoint.
|
|
16
|
-
- [MCP
|
|
16
|
+
- [MCP counts](../reference/mcp.md#the-tools) (beta): AI clients count the records a filter finds, in all or per value of a field such as a status, without reading the records.
|
|
17
|
+
- [MCP app icon](../reference/mcp.md#the-apps-icon) (beta): AI clients show the app's own icon beside its name when someone adds its MCP endpoint.
|
|
18
|
+
- [MCP SQL for admins](../reference/mcp.md#sql-for-admins): People with the `admin` or `ai` role run read-only SQL over MCP when you turn it on.
|
|
19
|
+
- [MCP writes](../reference/mcp.md#writes) (beta): When turned on, for every resource or only those you list, AI clients create, update and delete records after the person approves each write.
|
|
17
20
|
- [Small MCP write results](../reference/mcp.md#what-a-write-returns): A write over MCP returns the record's key, new ETag, the fields written and the read-only fields that changed, not the whole record; `fields` asks for more.
|
|
18
|
-
- [MCP writes without approval](../reference/mcp.md#writes-without-approval): Roles you name, such as service accounts, write over MCP without being asked to approve each write; everyone else is still asked.
|
|
21
|
+
- [MCP writes without approval](../reference/mcp.md#writes-without-approval) (beta): Roles you name, such as service accounts, write over MCP without being asked to approve each write; everyone else is still asked.
|
|
19
22
|
- [MCP comments](../reference/mcp.md#comments): AI clients read a record's comments, and with MCP writes on add one after the person approves it.
|
|
20
23
|
- [MCP batch writes](../reference/mcp.md#batch-writes): When MCP writes are on, AI clients make many creates, updates and deletes across resources in one transaction, after the person approves the batch.
|
|
21
24
|
- [Connected apps](../reference/auth.md#oauth-for-mcp-clients): People allow an AI client to sign in, and disconnect it later.
|
|
@@ -6,6 +6,8 @@ Develop, build and run an app, on servers, in containers and on phones.
|
|
|
6
6
|
|
|
7
7
|
- [Node and Bun](../reference/cli.md#how-it-runs): The `protobase` command runs on Node 22.12 or later or on Bun, and a bundle runs on both unless it is built for Bun only.
|
|
8
8
|
- [Dev server](../reference/cli.md#dev): The admin app and the API on one port, with changes shown without a restart.
|
|
9
|
+
- [Dev server without jobs](../reference/cli.md#dev): `protobase dev --no-jobs` runs no jobs and delivers no events, webhooks or pushes, to develop against a hosted app's database without doing its work twice.
|
|
10
|
+
- [UI-only dev server](../reference/cli.md#ui-only) (beta): `protobase dev --api <url>` serves the app's UI from the project with the API of an app already running, such as its staging deploy, with no database.
|
|
9
11
|
- [Editor types](../reference/cli.md#types): `protobase types` gives the editor and `tsc` the types of the CLI that builds the app, with nothing installed.
|
|
10
12
|
- [Project scripts](../reference/cli.md#run): `protobase run` runs a seed or a migration script on the CLI's packages.
|
|
11
13
|
- [Migrate command](../reference/cli.md#migrate): `protobase migrate` applies the project's SQL migrations in one transaction, with nothing installed.
|
|
@@ -6,10 +6,10 @@ Apps written for other systems that keep working on Protobase, and moving their
|
|
|
6
6
|
|
|
7
7
|
## Supabase
|
|
8
8
|
|
|
9
|
-
- [Supabase import](../reference/cli.md#import-supabase): Moves a Supabase project into Protobase, with schema, data, users, functions, storage and scheduled jobs.
|
|
10
|
-
- [Create a project on a Supabase database](../getting-started.md#on-
|
|
11
|
-
- [Supabase client API](../reference/supabase-rest.md): supabase-js `from()` and `rpc()` calls keep working, under the tables' RLS policies.
|
|
12
|
-
- [Supabase Auth](../reference/auth.md#supabase-js-and-supabasessr): `supabase.auth` and `@supabase/ssr` calls keep working.
|
|
13
|
-
- [Supabase Storage](../reference/supabase-storage.md): supabase-js storage calls keep working, over the app's file storage.
|
|
14
|
-
- [Supabase Realtime](../reference/supabase-realtime.md): Database changes, broadcast and presence through supabase-js channels, private ones authorized by the policies on `realtime.messages`, broadcasts sent from a server without joining a channel, and broadcasts from the database with `realtime.send()` and `realtime.broadcast_changes()`, across [several processes](../reference/supabase-realtime.md#several-processes) serving one app.
|
|
9
|
+
- [Supabase import](../reference/cli.md#import-supabase) (beta): Moves a Supabase project into Protobase, with schema, data, users, functions, storage and scheduled jobs, and a coding agent's skill finishes what its report leaves.
|
|
10
|
+
- [Create a project on a Supabase database](../getting-started.md#on-a-supabase-database) (beta): your Supabase users sign in to the new project with their own passwords, and Supabase's own auth schema stays as Supabase set it up.
|
|
11
|
+
- [Supabase client API](../reference/supabase-rest.md) (beta): supabase-js `from()` and `rpc()` calls keep working, under the tables' RLS policies.
|
|
12
|
+
- [Supabase Auth](../reference/auth.md#supabase-js-and-supabasessr) (beta): `supabase.auth` and `@supabase/ssr` calls keep working.
|
|
13
|
+
- [Supabase Storage](../reference/supabase-storage.md) (beta): supabase-js storage calls keep working, over the app's file storage.
|
|
14
|
+
- [Supabase Realtime](../reference/supabase-realtime.md) (beta): Database changes, broadcast and presence through supabase-js channels, private ones authorized by the policies on `realtime.messages`, broadcasts sent from a server without joining a channel, and broadcasts from the database with `realtime.send()` and `realtime.broadcast_changes()`, across [several processes](../reference/supabase-realtime.md#several-processes) serving one app.
|
|
15
15
|
- [Edge Functions](../guides/api-functions.md#functions-moved-from-supabase): Functions moved from Supabase answer at their old addresses.
|
|
@@ -18,7 +18,7 @@ Describe the tables of an existing Postgres database as resources, with their fi
|
|
|
18
18
|
- [Record validation](../reference/data-config.md#resource): A rule over the whole record runs on every create and update and reports each problem on the field it concerns.
|
|
19
19
|
- [Rollups and formulas](../reference/data-config.md#rollups-and-formulas) (beta): Fields that hold counts, sums, averages, minimums or maximums of related records, or values calculated from the record's own numbers, kept current by the database.
|
|
20
20
|
- [Sensitive fields](../reference/data-config.md#sensitive-fields): Values such as bank account numbers are left out of every response and shown masked. A permitted user reveals one value at a time, and each reveal is audited.
|
|
21
|
-
- [Recent strong sign-in](../reference/data-config.md#recent-strong-sign-in): A sensitive field can ask for a passkey or a second step in the last minutes before it is revealed. The record page asks the person to confirm it's them, then shows it.
|
|
21
|
+
- [Recent strong sign-in](../reference/data-config.md#recent-strong-sign-in): A sensitive field can ask for a passkey or a second step in the last minutes before it is revealed, or an emailed code for an account with neither. The record page asks the person to confirm it's them, then shows it.
|
|
22
22
|
- [Computed reveals](../reference/data-config.md#computed-reveals) (beta): A secret kept elsewhere, or built from several values, is revealed like a sensitive field, with the same access, audit and recent sign-in check, but made when it is asked for.
|
|
23
23
|
- [Encrypted fields](../reference/data-config.md#encrypted-fields): A sensitive text field is encrypted with the app's own key before it is stored, so dumps, backups and SQL readers see only ciphertext. A command encrypts the values a column already holds.
|
|
24
24
|
- [Encrypted fields in jobs and functions](../reference/data-config.md#reading-in-jobs-and-functions) (beta): A job or an API function reads an encrypted value the app must use itself, such as a share's password, and each read is audited like a reveal.
|
|
@@ -7,6 +7,8 @@ Mail the app sends, the PDF documents it makes, and forms for people without an
|
|
|
7
7
|
- [Email](../reference/email.md#configuration): The app sends mail through one SMTP server or a mailer you supply.
|
|
8
8
|
- [Built-in emails](../reference/email.md#protobases-own-mail): Sign-in codes, password resets, invitations and mentions are sent in the app's name.
|
|
9
9
|
- [Email templates](../reference/email.md#templates): Your own emails are React components with typed data, sent from jobs, hooks or functions.
|
|
10
|
+
- [Protobase's mail look](../reference/email.md#the-look-of-protobases-own-mail): Your emails use the layout, button, code and note that Protobase's own mail is built from.
|
|
11
|
+
- [Unsubscribe headers](../reference/email.md#templates): Your emails carry header fields of their own, such as a one-click unsubscribe link for bulk mail.
|
|
10
12
|
- [Attachments](../reference/email.md#attachments): A message carries files, such as an invoice's PDF, through the app's mail or someone's own mailbox.
|
|
11
13
|
- [PDF documents](../reference/pdf.md#documents): PDF files such as invoices and quotes are React components with typed data, rendered from jobs, hooks or functions and attached to mail as they are.
|
|
12
14
|
- [Mail from your own mailbox](../reference/email.md#from-someones-own-mailbox) (beta): People link their Gmail on the account page, and the app's functions send mail from it, with a clear error when the link is missing or expired.
|
|
@@ -10,10 +10,12 @@ React to changes, deliver them to other systems, and run work in the background.
|
|
|
10
10
|
- [Signed webhooks](../reference/webhooks.md#what-a-receiver-gets): Deliveries are signed under the Standard Webhooks spec.
|
|
11
11
|
- [Delivery retries](../reference/webhooks.md#delivery-and-retries): Failed deliveries are retried for about three days.
|
|
12
12
|
- [Webhooks in the admin app](../reference/webhooks.md#subscriptions-in-the-admin-app): Users create subscriptions, send test events and resend failures in the app.
|
|
13
|
+
- [Webhook delivery log](../reference/webhooks.md#subscriptions-in-the-admin-app): Every try of the admin app's subscriptions, successes too, is kept for 30 days with its status and timing, when the app turns it on.
|
|
13
14
|
- [Background jobs](../reference/jobs.md#enqueueing): Jobs are kept in the app's own Postgres and can be queued in the same transaction as a write, also from SQL.
|
|
14
15
|
- [Typed jobs](../reference/jobs.md#typed-names-and-payloads): Code that queues a job, and the job itself, are checked against the names and arguments the config declares as they compile.
|
|
15
16
|
- [Retries](../reference/jobs.md#failures): Failed jobs are retried with growing waits, and duplicates can be prevented with a key.
|
|
16
17
|
- [Ordered groups](../reference/jobs.md#ordered-groups): Jobs in one group run one at a time, in order.
|
|
17
18
|
- [Recurring jobs](../reference/jobs.md#recurring-jobs): Jobs run on a cron schedule in a chosen time zone.
|
|
18
19
|
- [Failed jobs page](../reference/jobs.md#failed-jobs-page): Admins see jobs that failed for good and retry or delete them.
|
|
20
|
+
- [Worker process](../reference/jobs.md#where-jobs-run): Jobs and event deliveries run beside the API, or in a worker process of their own from the same bundle.
|
|
19
21
|
- [Jobs on hosts that sleep](../reference/jobs.md#hosts-that-sleep): A host that stops idle apps can wake them when a job is due.
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
Uploaded files and images, where they are kept and how they are served.
|
|
6
6
|
|
|
7
7
|
- [File fields](../reference/files.md#what-the-column-holds): A text column holds an uploaded file, with limits on type and size.
|
|
8
|
+
- [File uploads in forms](../reference/files.md#in-forms): Forms upload a dropped or chosen file at once with a progress bar, and replace or remove it; tables show its name and a thumbnail.
|
|
8
9
|
- [File storage](../reference/files.md#providers): Files are kept on disk, or in a bucket of your own.
|
|
9
10
|
- [S3-compatible buckets](../reference/files.md#s3-compatible-buckets) (beta): Files are kept in any bucket that speaks the S3 API, when the app runs on Bun.
|
|
10
11
|
- [Files from a seed](../reference/files.md#writing-files-from-a-seed): A seed or another script writes files into the app's providers, read from its config and environment as the server reads them.
|
|
@@ -8,6 +8,7 @@ The app's colours, name, icon, language and installation.
|
|
|
8
8
|
- [Dark mode](../reference/theme.md#light-dark-or-the-system): Each user picks light, dark or the system setting.
|
|
9
9
|
- [App name, icon and colours](../reference/app-identity.md): The app's own name, icon and colours in the sidebar, browser tabs, `/favicon.ico`, home-screen icon and mail.
|
|
10
10
|
- [Logo and favicon](../reference/theme.md#logo-and-favicon): Your own favicon or logo replaces Protobase's.
|
|
11
|
+
- [Named icons](../reference/layouts.md#named-actions): Actions, key facts and card meta show an icon picked by name from a set, such as `play`, `bed` or `cpu`.
|
|
11
12
|
- [English and Dutch](../reference/languages.md): The built-in screens come in English and Dutch, with numbers and dates written to match.
|
|
12
13
|
- [Installable app](../reference/installable-app.md): The admin installs as an app on phones and computers, and says when the server cannot be reached.
|
|
13
14
|
- Reduced motion: When the system asks for less motion, animations and pulsing dots stop.
|
|
@@ -4,14 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
The sidebar, the user menu, the bottom bar on phones and the global search.
|
|
6
6
|
|
|
7
|
-
- [Sidebar](../reference/sidebar.md): Resources and pages in groups, in the order you set.
|
|
7
|
+
- [Sidebar](../reference/sidebar.md): Resources and pages in groups, in the order you set, and a drawer on phones.
|
|
8
8
|
- [Sidebar icons](../reference/sidebar.md#sidebar-icons): Each resource shows the icon its view names in the sidebar, bottom bar and profile menu.
|
|
9
|
-
- [Recent records](../reference/sidebar.md#recent-records): A sidebar entry lists a few records under it, with status dots or as titles alone, and marked per reader, such as unread ones in bold with a count.
|
|
9
|
+
- [Recent records](../reference/sidebar.md#recent-records) (beta): A sidebar entry lists a few records under it, with status dots or as titles alone, and marked per reader, such as unread ones in bold with a count.
|
|
10
10
|
- [Create link](../reference/sidebar.md#create-link): A sidebar entry links to its create form, such as "New message" under Direct messages, for whoever may create.
|
|
11
11
|
- [The current organization](../reference/sidebar.md#the-current-organization): A sidebar entry opens its list on the organization you work in, which you can clear to see every organization you are a member of.
|
|
12
12
|
- [User menu](../reference/user-menu.md): Account pages, help links, security settings, setting a status, the theme switch and Sign out.
|
|
13
13
|
- [Bottom bar](../reference/bottom-bar.md): On phones, tabs along the bottom for each role's main screens.
|
|
14
14
|
- [Global search](../reference/client.md#global-search): A search box, also on Cmd+K or Ctrl+K, finds records in every resource and jumps to pages and New forms.
|
|
15
|
-
- Breadcrumbs: The top bar shows the group, list and record you are on, each a link back.
|
|
16
|
-
-
|
|
15
|
+
- [Breadcrumbs](../reference/ui-config.md#breadcrumbs): The top bar shows the group, list and record you are on, each a link back, and on a phone a back button to the one before.
|
|
16
|
+
- [Tab titles](../reference/ui-config.md#names-and-columns): Browser tabs name the record, list or page they show, then the app.
|
|
17
|
+
- [Update notice](../reference/versioning.md#a-new-version-under-an-open-page): When the server runs a newer Protobase version, open pages offer a reload so the user can save their work first.
|
|
17
18
|
- Config refresh: When the config or the user's roles change, open pages load the new menus, columns and permissions without a reload.
|
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
Sign-in methods, users, roles, organizations and tokens.
|
|
6
6
|
|
|
7
7
|
- [Built-in sign-in](../reference/auth.md): Password sign-in, sessions and short-lived tokens, with no separate identity service.
|
|
8
|
+
- [Sign-in page](../reference/client.md#the-admin-app): The app signs people in at its own address, where the browser's Back returns to the step before, and signing in comes back to the page they were on.
|
|
8
9
|
- [First admin](../reference/auth.md#first-run): No account can be made over the web while the app has no users. You create the first admin from the terminal, and the last active admin cannot be deleted, demoted or blocked.
|
|
9
10
|
- [Users from a script](../reference/auth.md#first-run): A seed or another script of the project creates and lists users, and mints their tokens, on the app's own sign-in.
|
|
10
11
|
- [Versioned sign-in tables](../reference/auth.md#migrations): The sign-in tables come from numbered migrations that ship with Protobase, written as the project's own and recorded in the database, so a deploy checks them without comparing tables. Every feature has its tables from the start, so turning one on or off needs no migration.
|
|
11
12
|
- [Existing users table](../reference/auth.md#an-existing-users-table): Sign-in can use the users table another system keeps in the same database.
|
|
12
13
|
- [Users page](../reference/auth.md#users-page): Admins find people, set roles, block them, add accounts and invite people by email.
|
|
14
|
+
- [Account page](../reference/auth.md#account-page): Everyone signed in has an account page for their profile, sign-in and security, devices and connected apps, and you add, hide or reorder its tabs.
|
|
13
15
|
- [Password reset](../reference/auth.md#password-reset): People reset a forgotten password through an emailed link.
|
|
14
16
|
- [Password changes](../reference/auth.md#name-password-and-devices): Changing the password signs out every other device and MCP client, and emails the account that its password changed. Guesses at the current password are limited for each account.
|
|
15
17
|
- [Leaked passwords](../reference/auth.md#leaked-passwords): New passwords that appear in a known data breach are refused, when the app turns the check on.
|
|
@@ -19,8 +21,12 @@ Sign-in methods, users, roles, organizations and tokens.
|
|
|
19
21
|
- [Open sign-up](../reference/auth.md#open-sign-up): Anyone can create an account after confirming an emailed code.
|
|
20
22
|
- [Passkeys](../reference/auth.md#passkeys): People sign in with their device's face or fingerprint unlock, or with a security key.
|
|
21
23
|
- [Two-factor authentication](../reference/auth.md#two-factor-authentication): A second step with an authenticator app, backup codes or emailed codes.
|
|
24
|
+
- [Emailed confirmation code](../reference/auth.md#emailed-confirmation-code): Someone without a passkey or a second step confirms it's them with a code emailed on request, where they are, such as before a secret is shown.
|
|
25
|
+
- [Confirming an account change](../reference/auth.md#confirming-an-account-change): Adding a passkey, unlinking a provider or deleting the account from an older session asks to confirm it's them in place, never to sign out and sign in again.
|
|
22
26
|
- [Sign-in policy](../reference/auth.md#sign-in-policy): Admins choose which sign-in methods are on, off or required, and the server refuses a policy that would lock people out.
|
|
23
27
|
- [GitHub and social sign-in](../reference/auth.md#sign-in-with-github): People sign in with GitHub or another social provider.
|
|
28
|
+
- [Sign-in links](../reference/auth.md#sign-in-links-and-connected-accounts): A link signs someone in with a sign-in provider at once, without the sign-in page.
|
|
29
|
+
- [Connected accounts](../reference/auth.md#sign-in-links-and-connected-accounts): People link GitHub or another sign-in provider to their account from the account page.
|
|
24
30
|
- [SAML sign-in](../reference/auth.md#sign-in-with-saml): People sign in through any SAML provider.
|
|
25
31
|
- [OpenID Connect provider](../reference/auth.md#platform-sign-in-provider): A host adds an OpenID Connect provider for people who already have an account.
|
|
26
32
|
- [Staff sign-in](../reference/auth.md#staff-sign-in): The team that runs an app signs in as a person to help them, with a reason and a log entry.
|
|
@@ -11,11 +11,13 @@ React components, field types, pages and handlers of your own, loaded as plugins
|
|
|
11
11
|
- [Custom field types](../reference/plugins.md#field-types): Register how a kind of value is shown and edited, and pick it per field in a view.
|
|
12
12
|
- [Custom create pages](../reference/custom-components.md#create-pages): Replace a resource's create form with your own page, such as a booking flow.
|
|
13
13
|
- [Form parts](../reference/custom-components.md#form-parts): Build your own forms from the app's fields, pickers, switches, buttons and dialogs.
|
|
14
|
+
- [Markdown](../reference/custom-components.md#components): Show markdown in your own components, such as a chat message, as the app's markdown fields draw it.
|
|
15
|
+
- [Polling lists](../reference/custom-components.md#record-sections): A list your own component reads can refetch on an interval you set, to stay current.
|
|
14
16
|
- [Image carousel](../reference/custom-components.md#image-carousel): Step through screenshots or a record's files in your own components by swiping or with arrows, as cards step through photos.
|
|
15
17
|
- [Assistant parts](../reference/assistant.md#in-the-ui): The assistant's panel, chat messages, composer and action cards for your own components, with a frameless action card for a page that is already framed.
|
|
16
18
|
- [Chat parts](../reference/custom-components.md#chat-parts): A chat between people from the app's parts: messages with their author, avatar, time and a status line, day and new-message lines, a thread that loads earlier messages, keeps the reader's place, opens on the new-message line, says when the reader has read to the end and jumps to the end when they send, and a composer of several lines with @mentions and slots for an attach button and a tray of picked files, which sends files without text and takes pasted and dropped files.
|
|
17
|
-
- [Members' names](../reference/custom-components.md#members-names): The names and photos of the organization's members by user id, for message authors and @mentions in your own components; every member reads them, without email addresses.
|
|
18
|
-
- [Signed-in user](../reference/custom-components.md#
|
|
19
|
+
- [Members' names](../reference/custom-components.md#members-names) (beta): The names and photos of the organization's members by user id, for message authors and @mentions in your own components; every member reads them, without email addresses.
|
|
20
|
+
- [Signed-in user](../reference/custom-components.md#signed-in-user): The signed-in person's user id and name in your own components, such as for a filter on their own messages.
|
|
19
21
|
- [Action handlers](../reference/custom-components.md#action-handlers): Your own code for a named action, with the record, the API client and the action's inputs.
|
|
20
22
|
- [Shell slots](../reference/custom-components.md#shell-slots): Your own components in the top bar, in a panel beside every page, or as the sidebar logo.
|
|
21
23
|
- [Plugins](../reference/plugins.md#how-a-plugin-stays-working): Your React code is a plugin that the app loads at start and that keeps working across Protobase releases without changes.
|
|
@@ -61,8 +61,6 @@ Then it runs that `scaffold` in the project and prints the next steps. Without a
|
|
|
61
61
|
|
|
62
62
|
It never writes to the database. The first of the next steps does: `bun run db:migrate` adds the `auth` schema that sign-in keeps its users in, and the `migrations` schema, where the script records the migrations it applied in `migrations.applied`. A `public.schema_migrations` another migration tool keeps is left as it is.
|
|
63
63
|
|
|
64
|
-
On a Supabase database, which it knows by its `auth` and `storage` schemas and its `supabase_admin` role, as [`import supabase`](reference/cli.md#import-supabase) does, the `auth` schema is Supabase Auth's, and the project changes nothing in it. It leaves out `auth`, `storage` and the platform's other schemas, such as `realtime` and `supabase_migrations`, as Supabase's own tables. The project reads Supabase's users through a view over `auth.users` and keeps only its own sign-in data, in a schema of its own, `app_auth`: it points `protobase.config.ts` at the view, with [`users.keyTable`](reference/auth.md#a-view-over-another-systems-users), and writes migrations in place of those of Protobase's own user table: Better Auth's tables in `app_auth`, pointing at `auth.users`, then the view, its side table of the columns only Better Auth has, and each user's account with their Supabase password and social providers. `bun run db:migrate` applies them as the `postgres` role a Supabase connection string signs in as, and Supabase's users sign in with their own passwords, under their own ids. You can create, disable and delete users from either side, and a name changed in Supabase shows at once. <gap>A user who signs up through Supabase Auth later is a user of the project at once but cannot sign in with a password because sharing passwords with Supabase Auth is not implemented.</gap> Supabase's phone-only and anonymous users are not users of the project, since sign-in needs an email. A ban set in the project stays the project's own, while a ban set in Supabase holds in the project too.
|
|
65
|
-
|
|
66
64
|
```sh
|
|
67
65
|
cd shop
|
|
68
66
|
bun run db:migrate # the first write to the database
|
|
@@ -70,6 +68,14 @@ bun run protobase users create you@example.com --generate-password
|
|
|
70
68
|
bun run dev # http://localhost:5173
|
|
71
69
|
```
|
|
72
70
|
|
|
71
|
+
#### On a Supabase database
|
|
72
|
+
|
|
73
|
+
:::caution[Beta]
|
|
74
|
+
A user who signs up through Supabase Auth later cannot sign in to the project with a password yet, as below.
|
|
75
|
+
:::
|
|
76
|
+
|
|
77
|
+
On a Supabase database, which it knows by its `auth` and `storage` schemas and its `supabase_admin` role, as [`import supabase`](reference/cli.md#import-supabase) does, the `auth` schema is Supabase Auth's, and the project changes nothing in it. It leaves out `auth`, `storage` and the platform's other schemas, such as `realtime` and `supabase_migrations`, as Supabase's own tables. The project reads Supabase's users through a view over `auth.users` and keeps only its own sign-in data, in a schema of its own, `app_auth`: it points `protobase.config.ts` at the view, with [`users.keyTable`](reference/auth.md#a-view-over-another-systems-users), and writes migrations in place of those of Protobase's own user table: Better Auth's tables in `app_auth`, pointing at `auth.users`, then the view, its side table of the columns only Better Auth has, and each user's account with their Supabase password and social providers. `bun run db:migrate` applies them as the `postgres` role a Supabase connection string signs in as, and Supabase's users sign in with their own passwords, under their own ids. You can create, disable and delete users from either side, and a name changed in Supabase shows at once. <gap>A user who signs up through Supabase Auth later is a user of the project at once but cannot sign in with a password because sharing passwords with Supabase Auth is not implemented.</gap> Supabase's phone-only and anonymous users are not users of the project, since sign-in needs an email. A ban set in the project stays the project's own, while a ban set in Supabase holds in the project too.
|
|
78
|
+
|
|
73
79
|
## Admin over an existing database
|
|
74
80
|
|
|
75
81
|
For a Postgres database you already have, `npm create protobase <database-url>` makes the project, as [above](#on-an-existing-database). [Admin over an existing database](guides/existing-database.md) does it by hand, in a folder of your own: install the packages, scaffold the config from the tables, check it with `doctor`, add sign-in and its tables, create the first admin and run `protobase dev`.
|
|
@@ -419,9 +419,9 @@ export default defineFunction({ longQueries: true }, async (request, { db }) =>
|
|
|
419
419
|
|
|
420
420
|
[`protobase import supabase`](../reference/cli.md#import-supabase) converts `supabase/functions` into `functions/`. A converted function keeps Supabase's shape, `defineFunction({ supabase: true }, { fetch })`, and keeps working with `@supabase/server` and supabase-js:
|
|
421
421
|
|
|
422
|
-
- **At `/api/sb/functions/v1/<name>` too,** so `supabase.functions.invoke` callers do not change. Only a function with `supabase: true` answers there, and any other function answers `404`. There a protected function lets in a request with the app's publishable or secret key in the `apikey` header, and a user token of [`/api/sb/auth/v1`](../reference/auth.md#supabase-js-and-supabasessr) as the bearer, if any, as Supabase's `verify_jwt` does. A public function lets in anyone. The keys are
|
|
422
|
+
- **At `/api/sb/functions/v1/<name>` too,** so `supabase.functions.invoke` callers do not change. Only a function with `supabase: true` answers there, and any other function answers `404`. There a protected function lets in a request with the app's publishable or secret key in the `apikey` header, and a user token of [`/api/sb/auth/v1`](../reference/auth.md#supabase-js-and-supabasessr) as the bearer, if any, as Supabase's `verify_jwt` does. A public function lets in anyone. The keys are the app's [API keys](../reference/supabase-rest.md#keys-and-roles): without them, only public functions answer there. As on Supabase, the request's path starts at the function's name (`/<name>/more/parts`), and a CORS preflight reaches the function, which answers it.
|
|
423
423
|
- **Its context there** is `{ name, db, caller() }`, where `caller()` resolves to who the key and token make the request: `{ role: 'anon' }` for the publishable key alone, `{ role: 'app' }` for the secret key, or `{ role: 'authenticated', claims }`. There is no `session` and no `records`. The publishable key alone lets callers in, so give `supabase: true` only to functions written for Supabase's `verify_jwt`. Together with `roles` or `capability`, which the address cannot check, it stops startup.
|
|
424
|
-
- **Supabase's variables:** `protobase dev` sets `SUPABASE_URL` to the app's address with `/api/sb`, where supabase-js finds it, `SUPABASE_ANON_KEY
|
|
424
|
+
- **Supabase's variables:** `protobase dev` sets `SUPABASE_URL` to the app's address with `/api/sb`, where supabase-js finds it, `SUPABASE_ANON_KEY` and `SUPABASE_PUBLISHABLE_KEY` to the publishable key and `SUPABASE_PUBLISHABLE_KEYS` to `{"default":<publishable key>}`, `SUPABASE_SERVICE_ROLE_KEY` and `SUPABASE_SECRET_KEY` to the secret key and `SUPABASE_SECRET_KEYS` to `{"default":<secret key>}`, each only when the app has that key, `SUPABASE_JWKS_URL` to the key set of `/api/sb/auth/v1` when the app has sign-in, and `SUPABASE_DB_URL` to the database. A variable set in `.env` wins. Under `protobase serve`, set them where the host keeps its settings.
|
|
425
425
|
- **`waitUntil(promise)`** from `@protobase/server` takes the place of `EdgeRuntime.waitUntil`. The response goes out at once, and the process waits for the work when it shuts down gracefully. Work still running when the host kills the process is lost. On Protobase Cloud's Starter plan an app stops 30 seconds after its last request, with 10 seconds to finish, so move longer work into a [job](../reference/jobs.md).
|
|
426
426
|
|
|
427
427
|
## What functions are not
|
|
@@ -115,7 +115,7 @@ Get responses carry `permissions: { update, delete, fields }`, where `fields` ma
|
|
|
115
115
|
|
|
116
116
|
A field declared [`.sensitive()`](data-config.md#sensitive-fields) is in `/meta` (marked `sensitive: true`), in record `permissions.fields` and in create and update bodies, but in no list, get, `:search`, `:seek`, write or batch response, and `filter`, `order_by`, `fields` and the aggregates refuse it like an unknown field. `POST /{resource}/{key}:reveal` with `{ "field": "<name>" }` returns one record's value. It needs read access to the record and the field: another tenant's record, a row outside the caller's row filter or a key that does not exist is a `404`, a field the caller cannot read or that is not sensitive a `400`. An [encrypted field](data-config.md#encrypted-fields)'s reveal decrypts its value, and an empty one reveals `null`; a value that does not decrypt is a `500` reported to the operator, without an audit event. An update that changes the key of a record with encrypted fields is a `400` `key-change-unsupported`.
|
|
117
117
|
|
|
118
|
-
A field with [`.strongSignIn()`](data-config.md#recent-strong-sign-in) is a `403` `strong-sign-in-required`, whose problem carries `minutes`, until the caller's sign-in proved a passkey or a second step that recently. The same request reveals a resource's [computed reveals](data-config.md#computed-reveals) by name, whose value the server makes when asked.
|
|
118
|
+
A field with [`.strongSignIn()`](data-config.md#recent-strong-sign-in) is a `403` `strong-sign-in-required`, whose problem carries `minutes`, until the caller's sign-in proved a passkey or a second step that recently, or, for an account with neither, an [emailed confirmation code](auth.md#emailed-confirmation-code). The same request reveals a resource's [computed reveals](data-config.md#computed-reveals) by name, whose value the server makes when asked.
|
|
119
119
|
|
|
120
120
|
Before answering, the server publishes an audit event to the app's audit queue, and when publishing fails the reveal fails (`500`), so no value leaves without its event. The event never holds the value:
|
|
121
121
|
|
|
@@ -30,7 +30,7 @@ export default defineConfig({
|
|
|
30
30
|
|
|
31
31
|
## The icons
|
|
32
32
|
|
|
33
|
-
`protobase.favicon.svg` is the one source of the app's icon: the tab shows it, the [web manifest](installable-app.md#the-manifest) names it, and with it the sidebar's logo draws it
|
|
33
|
+
`protobase.favicon.svg` is the one source of the app's icon: the tab shows it, the [web manifest](installable-app.md#the-manifest) names it, and with it the sidebar's logo draws it in place of Protobase's mark, beside the app's name. A project's own [`shell.logo`](custom-components.md#shell-slots) still wins.
|
|
34
34
|
|
|
35
35
|
Some platforms take no SVG, so a project may put PNGs of the same icon beside it, each square at its size:
|
|
36
36
|
|