@polardbx/pxbaas 0.0.0-stage → 0.1.0-beta.10
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/README.md +223 -2
- package/bin/pxbaas.js +13 -0
- package/dist/app/local-index.d.ts +70 -0
- package/dist/app/local-index.d.ts.map +1 -0
- package/dist/app/local-index.js +140 -0
- package/dist/app/local-index.js.map +1 -0
- package/dist/app/preflight.d.ts +61 -0
- package/dist/app/preflight.d.ts.map +1 -0
- package/dist/app/preflight.js +115 -0
- package/dist/app/preflight.js.map +1 -0
- package/dist/app/spec-lint.d.ts +45 -0
- package/dist/app/spec-lint.d.ts.map +1 -0
- package/dist/app/spec-lint.js +423 -0
- package/dist/app/spec-lint.js.map +1 -0
- package/dist/app/templates.d.ts +167 -0
- package/dist/app/templates.d.ts.map +1 -0
- package/dist/app/templates.js +151 -0
- package/dist/app/templates.js.map +1 -0
- package/dist/cli/help-guides.d.ts +17 -0
- package/dist/cli/help-guides.d.ts.map +1 -0
- package/dist/cli/help-guides.js +132 -0
- package/dist/cli/help-guides.js.map +1 -0
- package/dist/cli/root.d.ts +3 -0
- package/dist/cli/root.d.ts.map +1 -0
- package/dist/cli/root.js +75 -0
- package/dist/cli/root.js.map +1 -0
- package/dist/commands/app/index.d.ts +8 -0
- package/dist/commands/app/index.d.ts.map +1 -0
- package/dist/commands/app/index.js +1907 -0
- package/dist/commands/app/index.js.map +1 -0
- package/dist/commands/auth/index.d.ts +3 -0
- package/dist/commands/auth/index.d.ts.map +1 -0
- package/dist/commands/auth/index.js +198 -0
- package/dist/commands/auth/index.js.map +1 -0
- package/dist/commands/completion/index.d.ts +3 -0
- package/dist/commands/completion/index.d.ts.map +1 -0
- package/dist/commands/completion/index.js +14 -0
- package/dist/commands/completion/index.js.map +1 -0
- package/dist/commands/data/index.d.ts +55 -0
- package/dist/commands/data/index.d.ts.map +1 -0
- package/dist/commands/data/index.js +407 -0
- package/dist/commands/data/index.js.map +1 -0
- package/dist/commands/db/index.d.ts +29 -0
- package/dist/commands/db/index.d.ts.map +1 -0
- package/dist/commands/db/index.js +500 -0
- package/dist/commands/db/index.js.map +1 -0
- package/dist/commands/doctor/index.d.ts +3 -0
- package/dist/commands/doctor/index.d.ts.map +1 -0
- package/dist/commands/doctor/index.js +305 -0
- package/dist/commands/doctor/index.js.map +1 -0
- package/dist/commands/functions/delete.d.ts +3 -0
- package/dist/commands/functions/delete.d.ts.map +1 -0
- package/dist/commands/functions/delete.js +55 -0
- package/dist/commands/functions/delete.js.map +1 -0
- package/dist/commands/functions/deploy.d.ts +3 -0
- package/dist/commands/functions/deploy.d.ts.map +1 -0
- package/dist/commands/functions/deploy.js +118 -0
- package/dist/commands/functions/deploy.js.map +1 -0
- package/dist/commands/functions/index.d.ts +3 -0
- package/dist/commands/functions/index.d.ts.map +1 -0
- package/dist/commands/functions/index.js +15 -0
- package/dist/commands/functions/index.js.map +1 -0
- package/dist/commands/functions/list.d.ts +6 -0
- package/dist/commands/functions/list.d.ts.map +1 -0
- package/dist/commands/functions/list.js +63 -0
- package/dist/commands/functions/list.js.map +1 -0
- package/dist/commands/functions/new.d.ts +7 -0
- package/dist/commands/functions/new.d.ts.map +1 -0
- package/dist/commands/functions/new.js +72 -0
- package/dist/commands/functions/new.js.map +1 -0
- package/dist/commands/gen/index.d.ts +3 -0
- package/dist/commands/gen/index.d.ts.map +1 -0
- package/dist/commands/gen/index.js +323 -0
- package/dist/commands/gen/index.js.map +1 -0
- package/dist/commands/guide/index.d.ts +8 -0
- package/dist/commands/guide/index.d.ts.map +1 -0
- package/dist/commands/guide/index.js +20 -0
- package/dist/commands/guide/index.js.map +1 -0
- package/dist/commands/instance/index.d.ts +8 -0
- package/dist/commands/instance/index.d.ts.map +1 -0
- package/dist/commands/instance/index.js +481 -0
- package/dist/commands/instance/index.js.map +1 -0
- package/dist/commands/login/index.d.ts +3 -0
- package/dist/commands/login/index.d.ts.map +1 -0
- package/dist/commands/login/index.js +145 -0
- package/dist/commands/login/index.js.map +1 -0
- package/dist/commands/logout/index.d.ts +3 -0
- package/dist/commands/logout/index.d.ts.map +1 -0
- package/dist/commands/logout/index.js +41 -0
- package/dist/commands/logout/index.js.map +1 -0
- package/dist/commands/profile/import-env.d.ts +18 -0
- package/dist/commands/profile/import-env.d.ts.map +1 -0
- package/dist/commands/profile/import-env.js +206 -0
- package/dist/commands/profile/import-env.js.map +1 -0
- package/dist/commands/profile/index.d.ts +3 -0
- package/dist/commands/profile/index.d.ts.map +1 -0
- package/dist/commands/profile/index.js +124 -0
- package/dist/commands/profile/index.js.map +1 -0
- package/dist/commands/rest/index.d.ts +3 -0
- package/dist/commands/rest/index.d.ts.map +1 -0
- package/dist/commands/rest/index.js +102 -0
- package/dist/commands/rest/index.js.map +1 -0
- package/dist/commands/skill/index.d.ts +11 -0
- package/dist/commands/skill/index.d.ts.map +1 -0
- package/dist/commands/skill/index.js +228 -0
- package/dist/commands/skill/index.js.map +1 -0
- package/dist/commands/status/index.d.ts +3 -0
- package/dist/commands/status/index.d.ts.map +1 -0
- package/dist/commands/status/index.js +59 -0
- package/dist/commands/status/index.js.map +1 -0
- package/dist/commands/storage/index.d.ts +3 -0
- package/dist/commands/storage/index.d.ts.map +1 -0
- package/dist/commands/storage/index.js +400 -0
- package/dist/commands/storage/index.js.map +1 -0
- package/dist/commands/tenant/index.d.ts +19 -0
- package/dist/commands/tenant/index.d.ts.map +1 -0
- package/dist/commands/tenant/index.js +667 -0
- package/dist/commands/tenant/index.js.map +1 -0
- package/dist/commands/whoami/index.d.ts +3 -0
- package/dist/commands/whoami/index.d.ts.map +1 -0
- package/dist/commands/whoami/index.js +101 -0
- package/dist/commands/whoami/index.js.map +1 -0
- package/dist/credentials/commit.d.ts +38 -0
- package/dist/credentials/commit.d.ts.map +1 -0
- package/dist/credentials/commit.js +57 -0
- package/dist/credentials/commit.js.map +1 -0
- package/dist/credentials/credential-store.d.ts +86 -0
- package/dist/credentials/credential-store.d.ts.map +1 -0
- package/dist/credentials/credential-store.js +282 -0
- package/dist/credentials/credential-store.js.map +1 -0
- package/dist/credentials/env-file.d.ts +85 -0
- package/dist/credentials/env-file.d.ts.map +1 -0
- package/dist/credentials/env-file.js +193 -0
- package/dist/credentials/env-file.js.map +1 -0
- package/dist/credentials/formatter.d.ts +39 -0
- package/dist/credentials/formatter.d.ts.map +1 -0
- package/dist/credentials/formatter.js +79 -0
- package/dist/credentials/formatter.js.map +1 -0
- package/dist/credentials/index.d.ts +13 -0
- package/dist/credentials/index.d.ts.map +1 -0
- package/dist/credentials/index.js +13 -0
- package/dist/credentials/index.js.map +1 -0
- package/dist/credentials/jwt-introspect.d.ts +39 -0
- package/dist/credentials/jwt-introspect.d.ts.map +1 -0
- package/dist/credentials/jwt-introspect.js +64 -0
- package/dist/credentials/jwt-introspect.js.map +1 -0
- package/dist/credentials/masker.d.ts +23 -0
- package/dist/credentials/masker.d.ts.map +1 -0
- package/dist/credentials/masker.js +40 -0
- package/dist/credentials/masker.js.map +1 -0
- package/dist/credentials/profile.d.ts +95 -0
- package/dist/credentials/profile.d.ts.map +1 -0
- package/dist/credentials/profile.js +207 -0
- package/dist/credentials/profile.js.map +1 -0
- package/dist/credentials/tenant-guard.d.ts +16 -0
- package/dist/credentials/tenant-guard.d.ts.map +1 -0
- package/dist/credentials/tenant-guard.js +53 -0
- package/dist/credentials/tenant-guard.js.map +1 -0
- package/dist/credentials/tty-guard.d.ts +29 -0
- package/dist/credentials/tty-guard.d.ts.map +1 -0
- package/dist/credentials/tty-guard.js +79 -0
- package/dist/credentials/tty-guard.js.map +1 -0
- package/dist/db/apply-schema.d.ts +144 -0
- package/dist/db/apply-schema.d.ts.map +1 -0
- package/dist/db/apply-schema.js +359 -0
- package/dist/db/apply-schema.js.map +1 -0
- package/dist/db/introspect.d.ts +54 -0
- package/dist/db/introspect.d.ts.map +1 -0
- package/dist/db/introspect.js +140 -0
- package/dist/db/introspect.js.map +1 -0
- package/dist/db/sql-file.d.ts +104 -0
- package/dist/db/sql-file.d.ts.map +1 -0
- package/dist/db/sql-file.js +584 -0
- package/dist/db/sql-file.js.map +1 -0
- package/dist/instance/provision.d.ts +89 -0
- package/dist/instance/provision.d.ts.map +1 -0
- package/dist/instance/provision.js +139 -0
- package/dist/instance/provision.js.map +1 -0
- package/dist/instance/zero-store.d.ts +47 -0
- package/dist/instance/zero-store.d.ts.map +1 -0
- package/dist/instance/zero-store.js +91 -0
- package/dist/instance/zero-store.js.map +1 -0
- package/dist/instance/zero-whitelist.d.ts +39 -0
- package/dist/instance/zero-whitelist.d.ts.map +1 -0
- package/dist/instance/zero-whitelist.js +67 -0
- package/dist/instance/zero-whitelist.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +69 -0
- package/dist/main.js.map +1 -0
- package/dist/skill/agents-md.d.ts +25 -0
- package/dist/skill/agents-md.d.ts.map +1 -0
- package/dist/skill/agents-md.js +62 -0
- package/dist/skill/agents-md.js.map +1 -0
- package/dist/skill/assets.d.ts +38 -0
- package/dist/skill/assets.d.ts.map +1 -0
- package/dist/skill/assets.js +111 -0
- package/dist/skill/assets.js.map +1 -0
- package/dist/skill/hint.d.ts +39 -0
- package/dist/skill/hint.d.ts.map +1 -0
- package/dist/skill/hint.js +109 -0
- package/dist/skill/hint.js.map +1 -0
- package/dist/skill/hosts.d.ts +45 -0
- package/dist/skill/hosts.d.ts.map +1 -0
- package/dist/skill/hosts.js +96 -0
- package/dist/skill/hosts.js.map +1 -0
- package/dist/skill/installer.d.ts +100 -0
- package/dist/skill/installer.d.ts.map +1 -0
- package/dist/skill/installer.js +241 -0
- package/dist/skill/installer.js.map +1 -0
- package/dist/skill/ownership.d.ts +35 -0
- package/dist/skill/ownership.d.ts.map +1 -0
- package/dist/skill/ownership.js +52 -0
- package/dist/skill/ownership.js.map +1 -0
- package/dist/tenant/bundle.d.ts +66 -0
- package/dist/tenant/bundle.d.ts.map +1 -0
- package/dist/tenant/bundle.js +132 -0
- package/dist/tenant/bundle.js.map +1 -0
- package/dist/tenant/guide.d.ts +50 -0
- package/dist/tenant/guide.d.ts.map +1 -0
- package/dist/tenant/guide.js +152 -0
- package/dist/tenant/guide.js.map +1 -0
- package/dist/tenant/probe.d.ts +65 -0
- package/dist/tenant/probe.d.ts.map +1 -0
- package/dist/tenant/probe.js +159 -0
- package/dist/tenant/probe.js.map +1 -0
- package/dist/tenant/verify.d.ts +37 -0
- package/dist/tenant/verify.d.ts.map +1 -0
- package/dist/tenant/verify.js +132 -0
- package/dist/tenant/verify.js.map +1 -0
- package/dist/version.d.ts +29 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +65 -0
- package/dist/version.js.map +1 -0
- package/package.json +63 -3
- package/skills/pxbaas-app-spec/SKILL.md +165 -0
- package/skills/pxbaas-app-spec/references/deploy-options.md +158 -0
- package/skills/pxbaas-app-spec/references/guardrails-pitfalls.md +89 -0
- package/skills/pxbaas-app-spec/references/runtime-contract.md +151 -0
- package/skills/pxbaas-app-spec/references/ssr-session.md +121 -0
- package/skills/pxbaas-app-spec/references/templates-examples.md +311 -0
- package/skills/pxbaas-cli/SKILL.md +186 -0
- package/skills/pxbaas-cli/references/api-reference.md +68 -0
- package/skills/pxbaas-cli/references/app-deploy.md +186 -0
- package/skills/pxbaas-cli/references/auth-and-credentials.md +116 -0
- package/skills/pxbaas-cli/references/data-postgrest.md +101 -0
- package/skills/pxbaas-cli/references/db-and-meta.md +102 -0
- package/skills/pxbaas-cli/references/edge-functions.md +71 -0
- package/skills/pxbaas-cli/references/multi-tenant-apps.md +138 -0
- package/skills/pxbaas-cli/references/provision.md +101 -0
- package/skills/pxbaas-cli/references/safety-and-two-phase.md +78 -0
- package/skills/pxbaas-cli/references/storage.md +61 -0
- package/skills/pxbaas-cli/references/tenant-admin.md +122 -0
- package/skills/pxbaas-cli/references/troubleshooting.md +76 -0
- package/templates/webapp/api/index.ts +55 -0
- package/templates/webapp/csr-bff/README.md +81 -0
- package/templates/webapp/csr-bff/backend/index.ts +168 -0
- package/templates/webapp/csr-bff/db/rls.json +13 -0
- package/templates/webapp/csr-bff/db/schema.sql +49 -0
- package/templates/webapp/csr-bff/frontend/dist/admin.html +101 -0
- package/templates/webapp/csr-bff/frontend/dist/article.html +44 -0
- package/templates/webapp/csr-bff/frontend/dist/blog.js +206 -0
- package/templates/webapp/csr-bff/frontend/dist/blogger.html +53 -0
- package/templates/webapp/csr-bff/frontend/dist/index.html +70 -0
- package/templates/webapp/csr-bff/frontend/dist/login.html +56 -0
- package/templates/webapp/csr-bff/frontend/dist/profile.html +59 -0
- package/templates/webapp/csr-bff/frontend/dist/signup.html +78 -0
- package/templates/webapp/csr-bff/frontend/dist/style.css +27 -0
- package/templates/webapp/manifest.json +419 -0
- package/templates/webapp/mpa/about.html +29 -0
- package/templates/webapp/mpa/app.js +42 -0
- package/templates/webapp/mpa/index.html +42 -0
- package/templates/webapp/mpa/index.ts +60 -0
- package/templates/webapp/mpa/style.css +42 -0
- package/templates/webapp/react-vite/frontend/index.html +12 -0
- package/templates/webapp/react-vite/frontend/package-lock.json +1826 -0
- package/templates/webapp/react-vite/frontend/package.json +23 -0
- package/templates/webapp/react-vite/frontend/src/App.tsx +34 -0
- package/templates/webapp/react-vite/frontend/src/main.tsx +13 -0
- package/templates/webapp/react-vite/frontend/src/style.css +46 -0
- package/templates/webapp/react-vite/frontend/tsconfig.json +20 -0
- package/templates/webapp/react-vite/frontend/vite.config.ts +10 -0
- package/templates/webapp/react-vite-hono/backend/index.ts +38 -0
- package/templates/webapp/react-vite-hono/frontend/index.html +12 -0
- package/templates/webapp/react-vite-hono/frontend/package-lock.json +1826 -0
- package/templates/webapp/react-vite-hono/frontend/package.json +23 -0
- package/templates/webapp/react-vite-hono/frontend/src/App.tsx +47 -0
- package/templates/webapp/react-vite-hono/frontend/src/main.tsx +13 -0
- package/templates/webapp/react-vite-hono/frontend/src/style.css +66 -0
- package/templates/webapp/react-vite-hono/frontend/tsconfig.json +20 -0
- package/templates/webapp/react-vite-hono/frontend/vite.config.ts +10 -0
- package/templates/webapp/spa/index.ts +119 -0
- package/templates/webapp/ssr-auth/backend/index.ts +133 -0
- package/templates/webapp/ssr-auth/frontend/dist/index.html +15 -0
- package/templates/webapp/ssr-blog/README.md +111 -0
- package/templates/webapp/ssr-blog/backend/index.ts +764 -0
- package/templates/webapp/ssr-blog/db/rls.json +13 -0
- package/templates/webapp/ssr-blog/db/schema.sql +51 -0
- package/templates/webapp/ssr-blog/frontend/dist/index.html +39 -0
- package/templates/webapp/ssr-frameworks/README.md +64 -0
- package/templates/webapp/ssr-frameworks/backend/index.ts +241 -0
- package/templates/webapp/ssr-frameworks/frontend/dist/index.html +31 -0
- package/templates/webapp/ssr-hono/backend/index.ts +111 -0
- package/templates/webapp/ssr-hono/frontend/dist/index.html +12 -0
- package/templates/webapp/ssr-shop/README.md +80 -0
- package/templates/webapp/ssr-shop/backend/index.ts +491 -0
- package/templates/webapp/ssr-shop/db/rls.json +16 -0
- package/templates/webapp/ssr-shop/db/schema.sql +90 -0
- package/templates/webapp/ssr-shop/frontend/dist/index.html +12 -0
- package/templates/webapp/vanilla-static/frontend/dist/app.js +6 -0
- package/templates/webapp/vanilla-static/frontend/dist/index.html +19 -0
- package/templates/webapp/vanilla-static/frontend/dist/style.css +43 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pxbaas-react-vite-hono-template",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"react": "18.3.1",
|
|
13
|
+
"react-dom": "18.3.1",
|
|
14
|
+
"react-router-dom": "6.28.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/react": "18.3.12",
|
|
18
|
+
"@types/react-dom": "18.3.1",
|
|
19
|
+
"@vitejs/plugin-react": "4.3.4",
|
|
20
|
+
"typescript": "5.6.3",
|
|
21
|
+
"vite": "5.4.21"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Link, Route, Routes } from 'react-router-dom';
|
|
3
|
+
|
|
4
|
+
function Home() {
|
|
5
|
+
const [result, setResult] = useState('Not checked');
|
|
6
|
+
|
|
7
|
+
async function checkApi() {
|
|
8
|
+
const response = await fetch('./api/health');
|
|
9
|
+
const body = await response.json();
|
|
10
|
+
setResult(body.ok ? 'Backend is ready' : 'Backend returned an error');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<section>
|
|
15
|
+
<p className="eyebrow">PolarDB-X BaaS</p>
|
|
16
|
+
<h1>{{APP_NAME}}</h1>
|
|
17
|
+
<p>React and Hono are served together from one stable App mount.</p>
|
|
18
|
+
<div className="actions">
|
|
19
|
+
<button type="button" onClick={checkApi}>Check API</button>
|
|
20
|
+
<Link className="button secondary" to="/about">About</Link>
|
|
21
|
+
</div>
|
|
22
|
+
<output aria-live="polite">{result}</output>
|
|
23
|
+
</section>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function About() {
|
|
28
|
+
return (
|
|
29
|
+
<section>
|
|
30
|
+
<p className="eyebrow">Route</p>
|
|
31
|
+
<h1>HashRouter works</h1>
|
|
32
|
+
<p>The backend is available through the mount-relative ./api path.</p>
|
|
33
|
+
<Link className="button" to="/">Back home</Link>
|
|
34
|
+
</section>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default function App() {
|
|
39
|
+
return (
|
|
40
|
+
<main>
|
|
41
|
+
<Routes>
|
|
42
|
+
<Route path="/" element={<Home />} />
|
|
43
|
+
<Route path="/about" element={<About />} />
|
|
44
|
+
</Routes>
|
|
45
|
+
</main>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom/client';
|
|
3
|
+
import { HashRouter } from 'react-router-dom';
|
|
4
|
+
import App from './App';
|
|
5
|
+
import './style.css';
|
|
6
|
+
|
|
7
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
8
|
+
<React.StrictMode>
|
|
9
|
+
<HashRouter>
|
|
10
|
+
<App />
|
|
11
|
+
</HashRouter>
|
|
12
|
+
</React.StrictMode>,
|
|
13
|
+
);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color: #17212b;
|
|
3
|
+
background: #f4f7fa;
|
|
4
|
+
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
* {
|
|
8
|
+
box-sizing: border-box;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
body {
|
|
12
|
+
min-width: 320px;
|
|
13
|
+
min-height: 100vh;
|
|
14
|
+
margin: 0;
|
|
15
|
+
display: grid;
|
|
16
|
+
place-items: center;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
main {
|
|
20
|
+
width: min(42rem, calc(100vw - 3rem));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
section {
|
|
24
|
+
padding: 3rem;
|
|
25
|
+
border: 1px solid #d7e0e8;
|
|
26
|
+
border-radius: 1rem;
|
|
27
|
+
background: #fff;
|
|
28
|
+
box-shadow: 0 1rem 3rem rgb(23 33 43 / 10%);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.eyebrow {
|
|
32
|
+
color: #087f5b;
|
|
33
|
+
font-weight: 700;
|
|
34
|
+
letter-spacing: 0.08em;
|
|
35
|
+
text-transform: uppercase;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.actions {
|
|
39
|
+
display: flex;
|
|
40
|
+
gap: 0.75rem;
|
|
41
|
+
margin-top: 1rem;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
button,
|
|
45
|
+
.button {
|
|
46
|
+
display: inline-block;
|
|
47
|
+
padding: 0.7rem 1rem;
|
|
48
|
+
border: 0;
|
|
49
|
+
border-radius: 0.5rem;
|
|
50
|
+
color: #fff;
|
|
51
|
+
background: #087f5b;
|
|
52
|
+
font: inherit;
|
|
53
|
+
text-decoration: none;
|
|
54
|
+
cursor: pointer;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.secondary {
|
|
58
|
+
color: #087f5b;
|
|
59
|
+
background: #e6fcf5;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
output {
|
|
63
|
+
display: block;
|
|
64
|
+
min-height: 1.5rem;
|
|
65
|
+
margin-top: 1rem;
|
|
66
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
6
|
+
"allowJs": false,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"allowSyntheticDefaultImports": true,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"module": "ESNext",
|
|
13
|
+
"moduleResolution": "Bundler",
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"isolatedModules": true,
|
|
16
|
+
"noEmit": true,
|
|
17
|
+
"jsx": "react-jsx"
|
|
18
|
+
},
|
|
19
|
+
"include": ["src", "vite.config.ts"]
|
|
20
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// {{APP_NAME}} — self-contained single-file Edge Function (SPA + built-in JSON API).
|
|
2
|
+
//
|
|
3
|
+
// Spec: follow the `pxbaas-app-spec` skill — `pxbaas app deploy` lints this file against it.
|
|
4
|
+
//
|
|
5
|
+
// Deploy with: pxbaas app deploy {{SLUG}}
|
|
6
|
+
// Share URL: {endpoint}/functions/v1/{{SLUG}} (browser opens directly; deployed with verify_jwt=false)
|
|
7
|
+
// Built-in API: {endpoint}/functions/v1/{{SLUG}}?action=time
|
|
8
|
+
//
|
|
9
|
+
// Guardrails baked into this template (do NOT break them):
|
|
10
|
+
// - All assets are inline (no external CSS/JS, no root-absolute paths like "/x"),
|
|
11
|
+
// so nothing escapes the /functions/v1/ prefix onto Studio.
|
|
12
|
+
// - Dynamic calls use RELATIVE URLs (location.pathname + "?action=...") so the
|
|
13
|
+
// request returns through Kong -> proxy -> sandbox on the same path.
|
|
14
|
+
// - Reflected input is HTML-escaped (public URL => injection-safe).
|
|
15
|
+
// - CORS headers are set for browser calls.
|
|
16
|
+
|
|
17
|
+
function corsHeaders(): Record<string, string> {
|
|
18
|
+
return {
|
|
19
|
+
"Access-Control-Allow-Origin": "*",
|
|
20
|
+
"Access-Control-Allow-Methods": "POST, GET, OPTIONS",
|
|
21
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, apikey, X-Tenant-Id",
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function esc(s: string): string {
|
|
26
|
+
return s
|
|
27
|
+
.replace(/&/g, "&")
|
|
28
|
+
.replace(/</g, "<")
|
|
29
|
+
.replace(/>/g, ">")
|
|
30
|
+
.replace(/"/g, """);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function pageHtml(serverIso: string): string {
|
|
34
|
+
return `<!DOCTYPE html>
|
|
35
|
+
<html lang="en">
|
|
36
|
+
<head>
|
|
37
|
+
<meta charset="utf-8" />
|
|
38
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
39
|
+
<title>{{APP_NAME}}</title>
|
|
40
|
+
<style>
|
|
41
|
+
:root { color-scheme: light dark; }
|
|
42
|
+
body { font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
43
|
+
max-width: 720px; margin: 40px auto; padding: 0 20px; line-height: 1.6; }
|
|
44
|
+
.card { border: 1px solid #ddd; border-radius: 12px; padding: 16px 20px; margin: 16px 0; }
|
|
45
|
+
code, pre { background: #f4f4f5; padding: 2px 6px; border-radius: 4px; word-break: break-all; }
|
|
46
|
+
pre { padding: 12px; overflow: auto; }
|
|
47
|
+
button { font-size: 15px; padding: 8px 16px; border-radius: 8px; border: 1px solid #ccc; cursor: pointer; }
|
|
48
|
+
.big { font-size: 32px; font-weight: 700; }
|
|
49
|
+
.muted { color: #888; font-size: 13px; }
|
|
50
|
+
</style>
|
|
51
|
+
</head>
|
|
52
|
+
<body>
|
|
53
|
+
<h1>{{APP_NAME}}</h1>
|
|
54
|
+
<p class="muted">Served by a Deno Edge Function inside the BaaS sandbox (text/html), reverse-proxied via Kong <code>/functions/v1/</code>.</p>
|
|
55
|
+
|
|
56
|
+
<div class="card">
|
|
57
|
+
<div class="muted">Share URL (send to anyone; opens directly in a browser)</div>
|
|
58
|
+
<code id="url">loading…</code>
|
|
59
|
+
<div class="muted" style="margin-top:8px">Server-rendered time (proves dynamic SSR): ${esc(serverIso)}</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div class="card">
|
|
63
|
+
<div>① Client-side counter (inline JS)</div>
|
|
64
|
+
<div class="big" id="count">0</div>
|
|
65
|
+
<button onclick="inc()">+1</button>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div class="card">
|
|
69
|
+
<div>② Call the built-in backend API (relative path → same function)</div>
|
|
70
|
+
<button onclick="ping()">GET ?action=time</button>
|
|
71
|
+
<pre id="api" class="muted">Click to fetch server time</pre>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<script>
|
|
75
|
+
document.getElementById("url").textContent = location.origin + location.pathname;
|
|
76
|
+
let n = 0;
|
|
77
|
+
function inc() { n++; document.getElementById("count").textContent = n; }
|
|
78
|
+
async function ping() {
|
|
79
|
+
const el = document.getElementById("api");
|
|
80
|
+
el.textContent = "loading…";
|
|
81
|
+
try {
|
|
82
|
+
const r = await fetch(location.pathname + "?action=time", { headers: { "Accept": "application/json" } });
|
|
83
|
+
el.textContent = JSON.stringify(await r.json(), null, 2);
|
|
84
|
+
} catch (e) { el.textContent = "error: " + e; }
|
|
85
|
+
}
|
|
86
|
+
</script>
|
|
87
|
+
</body>
|
|
88
|
+
</html>`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function json(body: unknown, status = 200): Response {
|
|
92
|
+
return new Response(JSON.stringify(body), {
|
|
93
|
+
status,
|
|
94
|
+
headers: { "Content-Type": "application/json", ...corsHeaders() },
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
Deno.serve((req: Request) => {
|
|
99
|
+
const url = new URL(req.url);
|
|
100
|
+
|
|
101
|
+
if (req.method === "OPTIONS") {
|
|
102
|
+
return new Response(null, { status: 204, headers: corsHeaders() });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Built-in JSON API — extend with your own actions here.
|
|
106
|
+
const action = url.searchParams.get("action");
|
|
107
|
+
if (action === "time") {
|
|
108
|
+
return json({ ok: true, now: new Date().toISOString() });
|
|
109
|
+
}
|
|
110
|
+
if (action) {
|
|
111
|
+
return json({ ok: false, error: `unknown action: ${action}` }, 400);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Default: serve the single-page app.
|
|
115
|
+
return new Response(pageHtml(new Date().toISOString()), {
|
|
116
|
+
status: 200,
|
|
117
|
+
headers: { "Content-Type": "text/html; charset=utf-8", ...corsHeaders() },
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// {{APP_NAME}} — SSR webapp with proxy-injected session (方案 C).
|
|
2
|
+
//
|
|
3
|
+
// Spec: follow the `pxbaas-app-spec` skill and `references/ssr-session.md`.
|
|
4
|
+
// The proxy validates a per-mount scoped session cookie and injects the
|
|
5
|
+
// authenticated identity as TRUSTED headers; this worker never reads cookies,
|
|
6
|
+
// never issues Set-Cookie, and never puts tokens in URLs. Header names come
|
|
7
|
+
// from `capabilities.sessionHeaders` (the only source of truth).
|
|
8
|
+
//
|
|
9
|
+
// Deploy with: pxbaas app deploy {{SLUG}}
|
|
10
|
+
// SSR page: {app-url}/api/page?page=... (worker returns text/html)
|
|
11
|
+
// Login: POST {app-url}/_session/login (proxy sets the scoped cookie)
|
|
12
|
+
// Logout: POST {app-url}/_session/logout (proxy clears it)
|
|
13
|
+
|
|
14
|
+
// Trusted identity headers (proxy-injected; client-supplied values are stripped).
|
|
15
|
+
const HDR_USER_ID = "x-app-user-id";
|
|
16
|
+
const HDR_USER_TOKEN = "x-app-user-token";
|
|
17
|
+
const HDR_USER_EMAIL = "x-app-user-email";
|
|
18
|
+
|
|
19
|
+
function cors(): Record<string, string> {
|
|
20
|
+
return {
|
|
21
|
+
"Access-Control-Allow-Origin": "*",
|
|
22
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
23
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, apikey, X-Tenant-Id",
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function esc(s: unknown): string {
|
|
27
|
+
return String(s == null ? "" : s)
|
|
28
|
+
.split("&").join("&")
|
|
29
|
+
.split("<").join("<")
|
|
30
|
+
.split(">").join(">")
|
|
31
|
+
.split('"').join(""");
|
|
32
|
+
}
|
|
33
|
+
function html(body: string, status = 200): Response {
|
|
34
|
+
return new Response(body, {
|
|
35
|
+
status,
|
|
36
|
+
headers: { "Content-Type": "text/html; charset=utf-8", ...cors() },
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function json(body: unknown, status = 200): Response {
|
|
40
|
+
return new Response(JSON.stringify(body), {
|
|
41
|
+
status,
|
|
42
|
+
headers: { "Content-Type": "application/json; charset=utf-8", ...cors() },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Server-side data access. Uses the injected user token (per-user RLS) when
|
|
47
|
+
// present, else the anon key for public reads. Data-plane base contract
|
|
48
|
+
// (edge-runtime app-env.ts, MR #30497903): the platform injects
|
|
49
|
+
// SUPABASE_INTERNAL_URL (the only sandbox-reachable base, DX-12 derived
|
|
50
|
+
// platform-side) and SUPABASE_ANON_KEY. SUPABASE_URL is render-only and MUST NOT
|
|
51
|
+
// be used for fetch. Empty base when the sandbox image predates the injection.
|
|
52
|
+
function base(): string {
|
|
53
|
+
return (Deno.env.get("SUPABASE_INTERNAL_URL") || "").replace(/\/+$/, "");
|
|
54
|
+
}
|
|
55
|
+
function anon(): string {
|
|
56
|
+
return Deno.env.get("SUPABASE_ANON_KEY") || "";
|
|
57
|
+
}
|
|
58
|
+
async function restGet(table: string, query: string, userToken?: string | null) {
|
|
59
|
+
const r = await fetch(`${base()}/rest/v1/${table}?${query}`, {
|
|
60
|
+
headers: {
|
|
61
|
+
apikey: anon(),
|
|
62
|
+
Authorization: "Bearer " + (userToken || anon()),
|
|
63
|
+
"Content-Type": "application/json",
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
if (!r.ok) throw new Error(`REST ${r.status}`);
|
|
67
|
+
return await r.json();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface Who {
|
|
71
|
+
userId: string | null;
|
|
72
|
+
token: string | null;
|
|
73
|
+
email: string | null;
|
|
74
|
+
}
|
|
75
|
+
function who(req: Request): Who {
|
|
76
|
+
// Absent headers = anonymous (graceful degradation on instances without the
|
|
77
|
+
// capability, and on the /functions/v1 entry which has no injected headers).
|
|
78
|
+
return {
|
|
79
|
+
userId: req.headers.get(HDR_USER_ID),
|
|
80
|
+
token: req.headers.get(HDR_USER_TOKEN),
|
|
81
|
+
email: req.headers.get(HDR_USER_EMAIL),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function pageShell(title: string, inner: string, w: Who): string {
|
|
86
|
+
const nav = w.userId
|
|
87
|
+
? `<a href="?page=index">Home</a> <a href="?page=me">My area</a> <span>· ${esc(w.email)}</span>
|
|
88
|
+
<form method="post" action="./_session/logout" style="display:inline"><button type="submit">Sign out</button></form>`
|
|
89
|
+
: `<a href="?page=index">Home</a>
|
|
90
|
+
<form method="post" action="./_session/login" style="display:inline">
|
|
91
|
+
<input name="email" type="email" placeholder="email" required>
|
|
92
|
+
<input name="password" type="password" placeholder="password" required>
|
|
93
|
+
<button type="submit">Sign in</button>
|
|
94
|
+
</form>`;
|
|
95
|
+
return `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">
|
|
96
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
97
|
+
<title>${esc(title)}</title>
|
|
98
|
+
<style>body{font:16px/1.6 system-ui,sans-serif;max-width:860px;margin:24px auto;padding:0 20px}
|
|
99
|
+
header{display:flex;gap:14px;align-items:center;flex-wrap:wrap;border-bottom:1px solid #eee;padding-bottom:10px}
|
|
100
|
+
header form{margin-left:auto;display:flex;gap:6px}input{padding:6px 8px}</style></head>
|
|
101
|
+
<body><header><strong>🧩 {{APP_NAME}}</strong>${nav}</header><main>${inner}</main></body></html>`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
Deno.serve(async (req: Request): Promise<Response> => {
|
|
105
|
+
if (req.method === "OPTIONS") return new Response(null, { status: 204, headers: cors() });
|
|
106
|
+
const url = new URL(req.url);
|
|
107
|
+
const action = url.searchParams.get("action");
|
|
108
|
+
if (action === "health" || action === "time") return json({ ok: true, now: new Date().toISOString() });
|
|
109
|
+
|
|
110
|
+
const w = who(req);
|
|
111
|
+
const page = url.searchParams.get("page") || "index";
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
if (page === "me") {
|
|
115
|
+
if (!w.userId) {
|
|
116
|
+
return html(pageShell("Sign in", `<p>Sign in above; the proxy sets a per-mount cookie and
|
|
117
|
+
injects your identity as trusted headers on the next request.</p>`, w));
|
|
118
|
+
}
|
|
119
|
+
// Authenticated SSR: fetch the user's own rows server-side (RLS enforced).
|
|
120
|
+
const rows = await restGet("<table>", "select=*&limit=50", w.token);
|
|
121
|
+
const list = (rows as Array<Record<string, unknown>>)
|
|
122
|
+
.map((r) => `<li>${esc(JSON.stringify(r))}</li>`).join("");
|
|
123
|
+
return html(pageShell("My area", `<h1>My area (SSR, as ${esc(w.email)})</h1><ul>${list}</ul>`, w));
|
|
124
|
+
}
|
|
125
|
+
// Public SSR page.
|
|
126
|
+
const rows = await restGet("<table>", "select=*&limit=50");
|
|
127
|
+
const list = (rows as Array<Record<string, unknown>>)
|
|
128
|
+
.map((r) => `<li>${esc(JSON.stringify(r))}</li>`).join("");
|
|
129
|
+
return html(pageShell("{{APP_NAME}}", `<h1>{{APP_NAME}} (SSR)</h1><ul>${list}</ul>`, w));
|
|
130
|
+
} catch (e) {
|
|
131
|
+
return html(pageShell("Error", `<p>SSR error: ${esc(String((e as Error)?.message || e))}</p>`, w), 500);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>{{APP_NAME}} — entering</title>
|
|
7
|
+
<meta http-equiv="refresh" content="0; url=./api/page?page=index">
|
|
8
|
+
<style>body{font:16px/1.6 system-ui,sans-serif;color:#6b7280;text-align:center;padding-top:60px}a{color:#5b6cff}</style>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<p>Entering {{APP_NAME}} (server-side rendered)…</p>
|
|
12
|
+
<p><a href="./api/page?page=index">Continue</a></p>
|
|
13
|
+
<script>location.replace("./api/page?page=index");</script>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# {{APP_NAME}} — 全站 SSR + 平台会话注入参考模板(Deno,零 npm 依赖)
|
|
2
|
+
|
|
3
|
+
**形态**:**整站服务端渲染**。worker 经实例内网基址读 MySQL(`posts_ssr`),用字符串模板直出整页 HTML(列表 / 文章 / 博主 / 登录 / 注册 / 管理 / 资料),浏览器不做客户端取数。
|
|
4
|
+
**会话模型**:`proxy-injected`(方案 C)——登录由平台的 per-mount 会话端点完成,proxy 校验 HttpOnly cookie 后向 worker 注入 `X-App-User-Id / X-App-User-Token / X-App-User-Email / X-App-Session-Gen`;worker **只读注入头**,不读 cookie、不签发 cookie、不把 token 放进 URL、不做本地验签。
|
|
5
|
+
|
|
6
|
+
> 前置能力:`pxbaas app capabilities --json` 需报 `sessionMode: "proxy-injected-header"`、`persistentSession: true`。若实例不支持,登录页会明确提示而不是静默降级。
|
|
7
|
+
|
|
8
|
+
## 目录
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
{{APP_NAME}}/
|
|
12
|
+
├── app.json # schemaVersion 2;fallback=worker;ssrProbePath=/posts;backend.session=proxy-injected
|
|
13
|
+
├── backend/index.ts # 单文件自包含 worker(零 npm 依赖 → 冷启动仅数秒,无 SSR 探针风险)
|
|
14
|
+
├── frontend/dist/index.html # 静态入口壳,meta refresh 到 ./api/page?page=index
|
|
15
|
+
├── db/schema.sql # 幂等数据层:posts_ssr + 5 条 RLS + 3 篇种子
|
|
16
|
+
└── README.md
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 部署前必须做的一件事:填 DEPLOY CONFIG
|
|
20
|
+
|
|
21
|
+
`backend/index.ts` 顶部两个常量默认为空;留空时 worker 明确报错而不是猜测(刻意设计:绝不回落到任何真实实例,避免静默读写别人的数据)。
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
const INTERNAL_BASE = ""; // 目标实例内网基址:把公网 host 的 http./https. 中缀去掉
|
|
25
|
+
const ANON_KEY = ""; // 单租户=实例 anon;多租户=该租户 anon(自带 tenant_id claim)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`/api/page?action=diag` 会回报 `configReady` 与实际生效的基址,用于部署后自检。
|
|
29
|
+
|
|
30
|
+
## 会话链路(为什么长这样)
|
|
31
|
+
|
|
32
|
+
`/apps/{mount}/api/*` 走 `TRUSTED_INTERNAL` 凭据策略,proxy 会剥掉客户端的
|
|
33
|
+
`authorization / cookie / apikey / x-api-key`,因此:
|
|
34
|
+
|
|
35
|
+
- **不能**靠透传浏览器 Authorization(worker 拿不到);
|
|
36
|
+
- **不能**自己读 cookie(拿到的只可能是 proxy 共享 jar 的串味残留,会造成"退出无效/身份串号");
|
|
37
|
+
- **不能**把 token 放 URL(泄漏到历史/日志/Referer,且被 spec-lint 的 `session-no-url-token` 判为 blocker)。
|
|
38
|
+
|
|
39
|
+
唯一正确来源是注入头:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
const token = req.headers.get("x-app-user-token") || "";
|
|
43
|
+
const sub = req.headers.get("x-app-user-id") || "";
|
|
44
|
+
const email = req.headers.get("x-app-user-email") || "";
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
登录 / 注册 / 退出因此都在页面脚本里打平台端点(相对当前页 `/apps/{m}/api/page` 用 `../`):
|
|
48
|
+
|
|
49
|
+
| 动作 | 端点 | 说明 |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| 登录 | `POST ../_session/login` `{email,password}` | proxy 校验租户 GoTrue → 下发 `Set-Cookie: pxbaas-app-session; Path=/apps/{m}/; HttpOnly; SameSite=Lax` |
|
|
52
|
+
| 注册 | `POST ?page=signup`(worker 建号)→ 再 `POST ../_session/login` | 建号与建会话分两步,避免把密码写进任何服务端渲染的 HTML |
|
|
53
|
+
| 退出 | `POST ../_session/logout` | 由平台清除 cookie;worker 无权也无需清 |
|
|
54
|
+
|
|
55
|
+
> 用 curl 复验时不会执行 JS:退出需直接打 `POST /apps/{m}/_session/logout`。
|
|
56
|
+
> 在容器内以 `Host: 127.0.0.1:8000` 打 `/apps/*` 会得到 `APP_HOST_REJECTED`(预期),验证请从公网侧发起。
|
|
57
|
+
|
|
58
|
+
## 数据层
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
sed 's/__SCHEMA__/public/g' db/schema.sql # 单租户
|
|
62
|
+
sed 's/__SCHEMA__/tenant_acme/g' db/schema.sql # 多租户
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
RLS 元数据写**中心库** `supabase._mysqlrest_rls_*`,`table_schema` = 上面的 `__SCHEMA__`;写错静默失效。
|
|
66
|
+
|
|
67
|
+
## 部署
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pxbaas app deploy {{APP_NAME}} --dir . # appName 必须与 app.json 的 name 一致
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
走 curl Management API 时用显式嵌套 `filename=frontend/dist/index.html`、`filename=backend/index.ts`。
|
|
74
|
+
本模板零 npm 依赖,冷启动仅数秒,**不需要**预热即可通过 publish。
|
|
75
|
+
|
|
76
|
+
## 验收清单
|
|
77
|
+
|
|
78
|
+
| # | 检查 | 期望 |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| 1 | `GET /apps/{m}/` | 200 静态壳,`meta refresh` 到 `./posts` |
|
|
81
|
+
| 2 | `GET /apps/{m}/posts` | 200 `text/html`,种子文章服务端渲染,含 `data-ssr="v1"` |
|
|
82
|
+
| 3 | `GET /apps/{m}/api/posts` 与 `/api/page?page=index` | 均 200 且出数据(`/api` 形态 + 旧 query 兼容) |
|
|
83
|
+
| 3b | `GET /apps/{m}/api/page?action=diag` | `configReady:true`,基址为**内网**域名 |
|
|
84
|
+
| 4 | 匿名 `GET /apps/{m}/admin` | 跳转 `login`(未识别身份) |
|
|
85
|
+
| 5 | `POST /apps/{m}/_session/login` | 200 + `Set-Cookie pxbaas-app-session; Path=/apps/{m}/; HttpOnly` |
|
|
86
|
+
| 6 | **只带 cookie**(无 sid、无 Authorization)`GET /apps/{m}/admin` | 已登录 SSR:显示邮箱、"写作管理",且页面**不含任何 `sid=`** |
|
|
87
|
+
| 7 | 带 cookie 发文(`author_id`=自己 / 他人) | 201 且落库 author_id==sub / **403 PGRST302** |
|
|
88
|
+
| 8 | `POST /apps/{m}/_session/logout` 后 `GET /apps/{m}/admin` | 回匿名 |
|
|
89
|
+
| 9 | 客户端伪造 `X-App-User-Id/Email` | 被剥离,worker 看不到 |
|
|
90
|
+
| 10 | 双用户交替请求 | 各自只见自己(无串味) |
|
|
91
|
+
|
|
92
|
+
## 路由(路径形态,三种派发形式均兼容)
|
|
93
|
+
|
|
94
|
+
| 路径 | 页面 |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `/apps/{m}/` | 静态壳,`meta refresh` 到 `./posts` |
|
|
97
|
+
| `/apps/{m}/posts` | 广场(SSR 列表,按作者分组,每组最新 2 篇 + "共 N 篇") |
|
|
98
|
+
| `/apps/{m}/post/{id}` | 文章详情(SSR) |
|
|
99
|
+
| `/apps/{m}/blogger/{authorId}` | 博主主页(SSR) |
|
|
100
|
+
| `/apps/{m}/login` `/signup` `/admin` `/profile` `/logout` | 登录 / 注册 / 写作管理 / 资料 / 退出 |
|
|
101
|
+
| `/apps/{m}/api/<同上>` | 同一套路由的 `/api` 派发形态(等价) |
|
|
102
|
+
| `/apps/{m}/api/page?action=health\|diag` | 健康探针与部署自检(`configReady` / 基址可达性) |
|
|
103
|
+
|
|
104
|
+
worker 会同时兼容三种派发形态,因此同一份产物在任意入口都正确:
|
|
105
|
+
1. **静态未命中回落**:收到完整路径 `/apps/{m}/post/1`;
|
|
106
|
+
2. **`/api` 派发(strip 后)**:收到候选前缀形式 `/app_<uuid>_r_<rev>/posts`(既无 `api` 段也无 mount 段);
|
|
107
|
+
3. **legacy 函数入口**:`/functions/v1/{slug}/posts`。
|
|
108
|
+
|
|
109
|
+
实现要点:先剔除候选前缀与 `api` 标记,再从第一个**已知路由名**起截取 tail;链接前缀 `base` 在有 `X-App-Mount-Id` 时取挂载根 `/apps/{m}/`(与页面深度、派发形态无关),否则从真实路径回推。旧 `?page=index|article&id=…` 查询形态**仍兼容**(便于历史链接与 curl 复验)。
|
|
110
|
+
|
|
111
|
+
> 曾踩的坑:形态 2 的路径里既没有 `api` 段也没有 mount 段,按"有 api 段/含 mount id"推导 tail 会只剩 `["1"]`,导致 `/post/1` 落到 404 兜底页。判据是打印 worker 实际收到的 `pathname`(`?action=diag` 或临时日志),不要凭浏览器 URL 推断。
|