@celsoft/platform 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/adapters/cloudflare.d.ts +10 -1
  2. package/dist/adapters/cloudflare.js +46 -2
  3. package/dist/adapters/cloudflare.js.map +1 -1
  4. package/dist/adapters/drizzle.d.ts +15 -0
  5. package/dist/adapters/drizzle.js +111 -0
  6. package/dist/adapters/drizzle.js.map +1 -0
  7. package/dist/adapters/github.d.ts +1 -0
  8. package/dist/adapters/github.js +4 -0
  9. package/dist/adapters/github.js.map +1 -1
  10. package/dist/adapters/neon.d.ts +2 -0
  11. package/dist/adapters/neon.js +8 -0
  12. package/dist/adapters/neon.js.map +1 -1
  13. package/dist/adapters/process.d.ts +1 -7
  14. package/dist/adapters/process.js +0 -22
  15. package/dist/adapters/process.js.map +1 -1
  16. package/dist/adapters/resend.d.ts +1 -0
  17. package/dist/adapters/resend.js +3 -0
  18. package/dist/adapters/resend.js.map +1 -1
  19. package/dist/cli.js +71 -9
  20. package/dist/cli.js.map +1 -1
  21. package/dist/config.d.ts +11 -4
  22. package/dist/config.js +26 -9
  23. package/dist/config.js.map +1 -1
  24. package/dist/fakes.d.ts +24 -4
  25. package/dist/fakes.js +58 -6
  26. package/dist/fakes.js.map +1 -1
  27. package/dist/init.js +5 -5
  28. package/dist/init.js.map +1 -1
  29. package/dist/lifecycle.d.ts +7 -0
  30. package/dist/lifecycle.js +134 -18
  31. package/dist/lifecycle.js.map +1 -1
  32. package/dist/ownership.d.ts +16 -0
  33. package/dist/ownership.js +40 -0
  34. package/dist/ownership.js.map +1 -0
  35. package/dist/ports.d.ts +18 -3
  36. package/dist/runtime/auth.d.ts +1181 -6
  37. package/dist/runtime/auth.js +44 -14
  38. package/dist/runtime/auth.js.map +1 -1
  39. package/dist/runtime/files.js +4 -1
  40. package/dist/runtime/files.js.map +1 -1
  41. package/dist/runtime/guard.d.ts +12 -0
  42. package/dist/runtime/guard.js +42 -0
  43. package/dist/runtime/guard.js.map +1 -0
  44. package/dist/runtime/index.d.ts +18 -8
  45. package/dist/runtime/index.js +67 -19
  46. package/dist/runtime/index.js.map +1 -1
  47. package/dist/runtime/owner.d.ts +12 -0
  48. package/dist/runtime/owner.js +54 -0
  49. package/dist/runtime/owner.js.map +1 -0
  50. package/dist/runtime/screens.d.ts +4 -0
  51. package/dist/runtime/screens.js +158 -0
  52. package/dist/runtime/screens.js.map +1 -0
  53. package/dist/schema.d.ts +2775 -0
  54. package/dist/schema.js +129 -0
  55. package/dist/schema.js.map +1 -0
  56. package/dist/workflow.js +15 -5
  57. package/dist/workflow.js.map +1 -1
  58. package/dist/wrangler-config.d.ts +5 -0
  59. package/dist/wrangler-config.js +11 -2
  60. package/dist/wrangler-config.js.map +1 -1
  61. package/package.json +10 -4
  62. package/schema.json +18 -14
@@ -0,0 +1,158 @@
1
+ // Sign-in, sign-up and password screens every app with auth gets. Class names are part of the
2
+ // public interface: an app restyles them with its own stylesheet, loaded after this one.
3
+ export const SCREEN_PATHS = ["/login", "/signup", "/forgot", "/reset", "/recover"];
4
+ const email = { name: "email", label: "Email", type: "email", autocomplete: "email" };
5
+ const password = (autocomplete, label = "Password") => ({ name: "password", label, type: "password", autocomplete });
6
+ const SCREENS = {
7
+ "/login": {
8
+ title: "Sign in",
9
+ fields: [email, password("current-password")],
10
+ button: "Sign in",
11
+ links: [["/signup", "Create an account"], ["/forgot", "Forgot your password?"]],
12
+ },
13
+ "/signup": {
14
+ title: "Create an account",
15
+ fields: [{ name: "name", label: "Name", type: "text", autocomplete: "name" }, email, password("new-password")],
16
+ button: "Create account",
17
+ links: [["/login", "Already have an account? Sign in"]],
18
+ },
19
+ "/forgot": {
20
+ title: "Reset your password",
21
+ intro: "Enter your email and we'll send you a link to choose a new password.",
22
+ fields: [email],
23
+ button: "Send reset link",
24
+ links: [["/login", "Back to sign in"]],
25
+ },
26
+ "/reset": {
27
+ title: "Choose a new password",
28
+ fields: [password("new-password", "New password")],
29
+ button: "Change password",
30
+ links: [["/login", "Back to sign in"]],
31
+ },
32
+ "/recover": {
33
+ title: "Recover the owner account",
34
+ intro: "Use the recovery code you saved when you first signed in. It works once.",
35
+ fields: [email, { name: "code", label: "Recovery code", type: "text", autocomplete: "one-time-code" }, password("new-password", "New password")],
36
+ button: "Reset owner password",
37
+ links: [["/login", "Back to sign in"]],
38
+ },
39
+ };
40
+ export function isScreen(pathname) {
41
+ return SCREEN_PATHS.includes(pathname);
42
+ }
43
+ export function renderScreen(path, appName, stylesheet) {
44
+ const screen = SCREENS[path];
45
+ const fields = screen.fields
46
+ .map((f) => `<label class="platform-field"><span class="platform-label">${f.label}</span>` +
47
+ `<input class="platform-input" name="${f.name}" type="${f.type}" autocomplete="${f.autocomplete}" required${f.type === "password" ? ' minlength="8"' : ""}></label>`)
48
+ .join("");
49
+ const links = screen.links.map(([href, text]) => `<a href="${href}" data-carry>${text}</a>`).join("");
50
+ const html = `<!doctype html>
51
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
52
+ <title>${screen.title} · ${escape(appName)}</title>
53
+ <style>${STYLE}</style>${stylesheet ? `<link rel="stylesheet" href="${escape(stylesheet)}">` : ""}
54
+ </head><body class="platform-screen" data-page="${path.slice(1)}">
55
+ <main class="platform-card">
56
+ <p class="platform-app">${escape(appName)}</p>
57
+ <h1 class="platform-title">${screen.title}</h1>
58
+ ${screen.intro ? `<p class="platform-intro">${screen.intro}</p>` : ""}
59
+ <form class="platform-form" novalidate>${fields}
60
+ <p class="platform-error" role="alert" hidden></p>
61
+ <button class="platform-button" type="submit">${screen.button}</button></form>
62
+ <div class="platform-message" role="status" hidden></div>
63
+ <nav class="platform-links">${links}</nav>
64
+ </main>
65
+ <script>${SCRIPT}</script>
66
+ </body></html>`;
67
+ return new Response(html, { headers: { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" } });
68
+ }
69
+ function escape(text) {
70
+ return text.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
71
+ }
72
+ const STYLE = `
73
+ :root{--platform-bg:#f4f6f8;--platform-card:#fff;--platform-fg:#1b2430;--platform-muted:#5d6a78;--platform-line:#d5dce3;--platform-accent:#2554c7;--platform-on-accent:#fff;--platform-error:#b42318;color-scheme:light dark}
74
+ @media (prefers-color-scheme:dark){:root{--platform-bg:#11161c;--platform-card:#19212a;--platform-fg:#e6ebf0;--platform-muted:#9aa7b4;--platform-line:#2c3742;--platform-accent:#7aa2ff;--platform-on-accent:#0b1220;--platform-error:#ff8a80}}
75
+ *{box-sizing:border-box}
76
+ body.platform-screen{margin:0;min-height:100vh;display:grid;place-items:center;padding:24px 16px;background:var(--platform-bg);color:var(--platform-fg);font:16px/1.5 system-ui,-apple-system,"Segoe UI",sans-serif}
77
+ .platform-card{width:100%;max-width:380px;background:var(--platform-card);border:1px solid var(--platform-line);border-radius:12px;padding:28px 24px;display:flex;flex-direction:column;gap:16px}
78
+ .platform-app{margin:0;font-size:13px;letter-spacing:.06em;text-transform:uppercase;color:var(--platform-muted)}
79
+ .platform-title{margin:0;font-size:24px;line-height:1.2}
80
+ .platform-intro{margin:0;color:var(--platform-muted)}
81
+ .platform-form{display:flex;flex-direction:column;gap:14px}
82
+ .platform-field{display:flex;flex-direction:column;gap:6px;font-size:14px;font-weight:600}
83
+ .platform-input{font:inherit;font-weight:400;padding:10px 12px;border:1px solid var(--platform-line);border-radius:8px;background:var(--platform-bg);color:inherit}
84
+ .platform-input:focus-visible,.platform-button:focus-visible,.platform-links a:focus-visible{outline:2px solid var(--platform-accent);outline-offset:2px}
85
+ .platform-button{font:inherit;font-weight:600;padding:11px 14px;border:0;border-radius:8px;background:var(--platform-accent);color:var(--platform-on-accent);cursor:pointer}
86
+ .platform-button[disabled]{opacity:.6;cursor:progress}
87
+ .platform-error{margin:0;color:var(--platform-error);font-size:14px}
88
+ .platform-message{display:flex;flex-direction:column;gap:12px}
89
+ .platform-message p{margin:0}
90
+ .platform-code{font:600 18px/1.4 ui-monospace,Menlo,monospace;letter-spacing:.04em;padding:12px;border:1px dashed var(--platform-line);border-radius:8px;text-align:center;word-break:break-all;user-select:all}
91
+ .platform-links{display:flex;flex-direction:column;gap:6px;font-size:14px}
92
+ .platform-links a{color:var(--platform-accent)}
93
+ `;
94
+ const SCRIPT = `
95
+ const page = document.body.dataset.page;
96
+ const params = new URLSearchParams(location.search);
97
+ const next = (p => p && p.startsWith("/") && !p.startsWith("//") ? p : "/")(params.get("next"));
98
+ const carried = new URLSearchParams([...params].filter(([k]) => k === "next" || k === "invitation")).toString();
99
+ for (const a of document.querySelectorAll("[data-carry]")) if (carried) a.href += "?" + carried;
100
+ const form = document.querySelector(".platform-form");
101
+ const error = document.querySelector(".platform-error");
102
+ const message = document.querySelector(".platform-message");
103
+
104
+ async function post(path, body) {
105
+ const r = await fetch(path, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body), credentials: "same-origin" });
106
+ const data = await r.json().catch(() => ({}));
107
+ if (!r.ok) throw new Error(r.status === 429 ? "Too many attempts. Wait a minute, then try again." : data.message || "That didn't work. Try again.");
108
+ return data || {};
109
+ }
110
+
111
+ function say(...parts) {
112
+ form.hidden = true;
113
+ message.replaceChildren(...parts.map(p => typeof p === "string" ? Object.assign(document.createElement("p"), { textContent: p }) : p));
114
+ message.hidden = false;
115
+ }
116
+
117
+ async function finish() {
118
+ const invitation = params.get("invitation");
119
+ if (invitation) await post("/api/auth/organization/accept-invitation", { invitationId: invitation }).catch(() => {});
120
+ const { code } = await post("/api/platform/recovery-code", {}).catch(() => ({}));
121
+ if (!code) return location.assign(next);
122
+ const box = Object.assign(document.createElement("p"), { className: "platform-code", textContent: code });
123
+ const go = Object.assign(document.createElement("button"), { className: "platform-button", type: "button", textContent: "I saved it, continue" });
124
+ go.onclick = () => location.assign(next);
125
+ say("You're the owner of this app. Save this recovery code somewhere safe: it resets your password if you ever lose access to your email, and it is shown only once.", box, go);
126
+ }
127
+
128
+ const actions = {
129
+ login: async v => { await post("/api/auth/sign-in/email", { email: v.email, password: v.password }); await finish(); },
130
+ signup: async v => {
131
+ const data = await post("/api/auth/sign-up/email", { name: v.name, email: v.email, password: v.password, callbackURL: "/login" + (carried ? "?" + carried : "") });
132
+ if (data.token) return finish();
133
+ say("Check your email for a link to confirm your address. It signs you in.");
134
+ },
135
+ forgot: async v => { await post("/api/auth/request-password-reset", { email: v.email, redirectTo: "/reset" }); say("If that address has an account, a reset link is on its way."); },
136
+ reset: async v => { await post("/api/auth/reset-password", { newPassword: v.password, token: params.get("token") }); say("Your password is changed. Sign in with it now."); },
137
+ recover: async v => {
138
+ const { ok } = await post("/api/platform/recover", { email: v.email, code: v.code, password: v.password });
139
+ if (!ok) throw new Error("That email and recovery code don't match.");
140
+ say("The owner password is changed and every session is signed out. Sign in with the new password; you'll get a new recovery code.");
141
+ },
142
+ };
143
+
144
+ form.addEventListener("submit", async e => {
145
+ e.preventDefault();
146
+ error.hidden = true;
147
+ if (!form.reportValidity()) return;
148
+ const button = form.querySelector("button");
149
+ button.disabled = true;
150
+ try { await actions[page](Object.fromEntries(new FormData(form))); }
151
+ catch (err) { error.textContent = err.message; error.hidden = false; }
152
+ finally { button.disabled = false; }
153
+ });
154
+
155
+ if (page === "reset" && (params.get("error") || !params.get("token"))) say("That reset link has expired or was already used. Ask for a new one from the sign-in page.");
156
+ if (page === "login") fetch("/api/auth/get-session", { credentials: "same-origin" }).then(r => r.json()).then(s => s && s.session && finish()).catch(() => {});
157
+ `;
158
+ //# sourceMappingURL=screens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screens.js","sourceRoot":"","sources":["../../src/runtime/screens.ts"],"names":[],"mappings":"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"}