mirror of
https://github.com/trailbaseio/trailbase.git
synced 2026-05-02 06:29:29 -05:00
Borrow more complete todo app from upstream TanStack/db as a more attractive example.
This commit is contained in:
@@ -1,16 +1,15 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" href="data:image/png;base64,iVBORw0KGgo=" />
|
||||
<link href="/src/index.css" rel="stylesheet" />
|
||||
<title>Local-First Todo Example</title>
|
||||
</head>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="data:image/png;base64,iVBORw0KGgo=">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Local-First Example</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -19,10 +19,12 @@
|
||||
"@tanstack/trailbase-db-collection": "^0.0.3",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"trailbase": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.31.0",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
#root {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.react:hover {
|
||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
||||
}
|
||||
|
||||
@keyframes logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
a:nth-of-type(2) .logo {
|
||||
animation: logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
@@ -1,40 +1,39 @@
|
||||
import { useState, type FormEvent } from "react";
|
||||
import { QueryClient } from "@tanstack/query-core";
|
||||
import { useLiveQuery, createCollection } from "@tanstack/react-db";
|
||||
import { queryCollectionOptions } from "@tanstack/query-db-collection";
|
||||
import { trailBaseCollectionOptions } from "@tanstack/trailbase-db-collection";
|
||||
|
||||
import { initClient, type Client } from "trailbase";
|
||||
import { useState } from "react";
|
||||
import type { FormEvent } from "react";
|
||||
|
||||
import "./App.css";
|
||||
import { getComplementaryColor } from "./lib/color";
|
||||
|
||||
const client: Client = initClient("http://localhost:4000");
|
||||
|
||||
type Data = {
|
||||
id: number | null;
|
||||
updated: number | null;
|
||||
data: string;
|
||||
};
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
const useTrailBase = true;
|
||||
|
||||
const dataCollection = useTrailBase
|
||||
type Config = {
|
||||
id: number;
|
||||
key: string;
|
||||
value: string;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
};
|
||||
|
||||
const configCollection = useTrailBase
|
||||
? createCollection(
|
||||
trailBaseCollectionOptions<Data>({
|
||||
recordApi: client.records<Data>("data"),
|
||||
trailBaseCollectionOptions<Config>({
|
||||
recordApi: client.records<Config>("config"),
|
||||
getKey: (item) => item.id ?? -1,
|
||||
parse: {},
|
||||
serialize: {},
|
||||
}),
|
||||
)
|
||||
: createCollection(
|
||||
queryCollectionOptions<Data>({
|
||||
id: "data",
|
||||
queryKey: ["data"],
|
||||
queryCollectionOptions<Config>({
|
||||
id: "config",
|
||||
queryKey: ["config"],
|
||||
queryFn: async () => {
|
||||
const data = client.records<Data>("data");
|
||||
const data = client.records<Config>("config");
|
||||
return (await data.list()).records;
|
||||
},
|
||||
getKey: (item) => item.id ?? -1,
|
||||
@@ -42,73 +41,226 @@ const dataCollection = useTrailBase
|
||||
}),
|
||||
);
|
||||
|
||||
function App() {
|
||||
const [input, setInput] = useState("");
|
||||
type Todo = {
|
||||
id: number;
|
||||
text: string;
|
||||
completed: boolean;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
};
|
||||
|
||||
const { data } = useLiveQuery((q) =>
|
||||
q.from({ record: dataCollection }).orderBy(({ record }) => record.updated),
|
||||
const todoCollection = useTrailBase
|
||||
? createCollection(
|
||||
trailBaseCollectionOptions<Todo>({
|
||||
recordApi: client.records<Todo>("todos"),
|
||||
getKey: (item) => item.id ?? -1,
|
||||
parse: {},
|
||||
serialize: {},
|
||||
}),
|
||||
)
|
||||
: createCollection(
|
||||
queryCollectionOptions<Todo>({
|
||||
id: "todos",
|
||||
queryKey: ["todos"],
|
||||
queryFn: async () => {
|
||||
const data = client.records<Todo>("todos");
|
||||
return (await data.list()).records;
|
||||
},
|
||||
getKey: (item) => item.id ?? -1,
|
||||
queryClient: queryClient,
|
||||
}),
|
||||
);
|
||||
|
||||
function now(): number {
|
||||
return Math.floor(Date.now() / 1000);
|
||||
}
|
||||
|
||||
function App() {
|
||||
// Get data using live queries with TrailBase collections
|
||||
const { data: todos } = useLiveQuery((q) =>
|
||||
q
|
||||
.from({ todo: todoCollection })
|
||||
.orderBy(({ todo }) => todo.created_at, `asc`),
|
||||
);
|
||||
|
||||
function handleSubmit(e: FormEvent) {
|
||||
e.preventDefault(); // Don't reload the page.
|
||||
const { data: configData } = useLiveQuery((q) =>
|
||||
q.from({ config: configCollection }),
|
||||
);
|
||||
|
||||
const form = e.target;
|
||||
const formData = new FormData(form as HTMLFormElement);
|
||||
const [newTodo, setNewTodo] = useState(``);
|
||||
|
||||
const formJson = Object.fromEntries(formData.entries());
|
||||
const text = formJson.text as string;
|
||||
|
||||
if (text) {
|
||||
dataCollection.insert({
|
||||
id: null,
|
||||
updated: null,
|
||||
data: formJson.text as string,
|
||||
});
|
||||
setInput("");
|
||||
// Define a type-safe helper function to get config values
|
||||
const getConfigValue = (key: string): string | undefined => {
|
||||
for (const config of configData) {
|
||||
if (config.key === key) {
|
||||
return config.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
// Define a helper function to update config values
|
||||
const setConfigValue = (key: string, value: string): void => {
|
||||
for (const config of configData) {
|
||||
if (config.key === key) {
|
||||
configCollection.update(config.id, (draft) => {
|
||||
draft.value = value;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If the config doesn't exist yet, create it
|
||||
configCollection.insert({
|
||||
id: Math.round(Math.random() * 1000000),
|
||||
key,
|
||||
value,
|
||||
created_at: now(),
|
||||
updated_at: now(),
|
||||
});
|
||||
};
|
||||
|
||||
const backgroundColor = getConfigValue(`backgroundColor`);
|
||||
const titleColor = getComplementaryColor(backgroundColor);
|
||||
|
||||
const handleColorChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newColor = e.target.value;
|
||||
setConfigValue(`backgroundColor`, newColor);
|
||||
};
|
||||
|
||||
const handleSubmit = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
const todo = newTodo.trim();
|
||||
setNewTodo(``);
|
||||
|
||||
if (todo) {
|
||||
todoCollection.insert({
|
||||
text: todo,
|
||||
completed: false,
|
||||
id: Math.round(Math.random() * 1000000),
|
||||
created_at: now(),
|
||||
updated_at: now(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const activeTodos = todos.filter((todo) => !todo.completed);
|
||||
const completedTodos = todos.filter((todo) => todo.completed);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Local First</h1>
|
||||
<main
|
||||
className="flex h-dvh justify-center overflow-auto py-8"
|
||||
style={{ backgroundColor }}
|
||||
>
|
||||
<div className="w-[550px]">
|
||||
<h1
|
||||
className="mb-4 text-center text-[70px] font-bold"
|
||||
style={{ color: titleColor }}
|
||||
>
|
||||
TrailBase Todo
|
||||
</h1>
|
||||
|
||||
<div className="card">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>id</th>
|
||||
<th>updated</th>
|
||||
<th>data</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<div className="flex justify-end py-4">
|
||||
<div className="flex items-center">
|
||||
<label
|
||||
htmlFor="colorPicker"
|
||||
className="mr-2 text-sm font-medium text-gray-700"
|
||||
style={{ color: titleColor }}
|
||||
>
|
||||
Background Color:
|
||||
</label>
|
||||
<input
|
||||
type="color"
|
||||
id="colorPicker"
|
||||
value={backgroundColor}
|
||||
onChange={handleColorChange}
|
||||
className="cursor-pointer rounded border border-gray-300"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<tbody>
|
||||
{data.map((d, idx) => (
|
||||
<tr key={`row-${idx}`}>
|
||||
<td>{d.id}</td>
|
||||
<td>{d.updated}</td>
|
||||
<td>{d.data}</td>
|
||||
</tr>
|
||||
<div className="relative bg-white shadow-[0_2px_4px_0_rgba(0,0,0,0.2),0_25px_50px_0_rgba(0,0,0,0.1)]">
|
||||
<form onSubmit={handleSubmit} className="relative">
|
||||
<button
|
||||
type="button"
|
||||
className="absolute h-full w-12 text-[30px] text-[#e6e6e6] hover:text-[#4d4d4d]"
|
||||
disabled={todos.length === 0}
|
||||
onClick={() => {
|
||||
const todosToToggle =
|
||||
activeTodos.length > 0 ? activeTodos : completedTodos;
|
||||
|
||||
todoCollection.update(
|
||||
todosToToggle.map((todo) => todo.id),
|
||||
(drafts) =>
|
||||
drafts.forEach(
|
||||
(draft) => (draft.completed = !draft.completed),
|
||||
),
|
||||
);
|
||||
}}
|
||||
>
|
||||
❯
|
||||
</button>
|
||||
<input
|
||||
type="text"
|
||||
value={newTodo}
|
||||
onChange={(e) => setNewTodo(e.target.value)}
|
||||
placeholder="What needs to be done?"
|
||||
className="box-border h-[64px] w-full border-none pr-4 pl-[60px] text-2xl font-light shadow-[inset_0_-2px_1px_rgba(0,0,0,0.03)]"
|
||||
/>
|
||||
</form>
|
||||
|
||||
<ul className="list-none">
|
||||
{todos.map((todo) => (
|
||||
<li
|
||||
key={`todo-${todo.id}`}
|
||||
className="group relative border-b border-[#ededed] last:border-none"
|
||||
>
|
||||
<div className="gap-1.2 flex h-[58px] items-center pl-[60px]">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={todo.completed}
|
||||
onChange={() =>
|
||||
todoCollection.update(todo.id, (draft) => {
|
||||
draft.completed = !draft.completed;
|
||||
})
|
||||
}
|
||||
className="absolute left-[12px] size-[40px] cursor-pointer"
|
||||
/>
|
||||
<label
|
||||
className={`block p-[15px] text-2xl transition-colors ${todo.completed ? `text-[#d9d9d9] line-through` : ``}`}
|
||||
>
|
||||
{todo.text}
|
||||
</label>
|
||||
<button
|
||||
onClick={() => todoCollection.delete(todo.id)}
|
||||
className="absolute right-[20px] hidden text-[30px] text-[#cc9a9a] transition-colors group-hover:block hover:text-[#af5b5e]"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</ul>
|
||||
|
||||
<footer className="flex h-[40px] items-center justify-between border-t border-[#e6e6e6] px-[15px] text-[14px] text-[#777]">
|
||||
<span>
|
||||
{`${activeTodos.length} ${activeTodos.length === 1 ? `item` : `items`} left`}
|
||||
</span>
|
||||
|
||||
{completedTodos.length > 0 && (
|
||||
<button
|
||||
onClick={() =>
|
||||
todoCollection.delete(completedTodos.map((todo) => todo.id))
|
||||
}
|
||||
className="hover:underline"
|
||||
>
|
||||
Clear completed
|
||||
</button>
|
||||
)}
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" onSubmit={handleSubmit}>
|
||||
<p className="read-the-docs">
|
||||
<input
|
||||
name="text"
|
||||
type="text"
|
||||
value={input}
|
||||
onInput={(e) => setInput(e.currentTarget.value)}
|
||||
/>
|
||||
|
||||
<button type="submit" disabled={input === ""}>
|
||||
submit
|
||||
</button>
|
||||
</p>
|
||||
</form>
|
||||
</>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,68 +1 @@
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
@import "tailwindcss";
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// Function to generate a complementary color
|
||||
export function getComplementaryColor(hexColor: string | undefined): string {
|
||||
// Default to a nice blue if no color is provided
|
||||
if (!hexColor) return `#3498db`;
|
||||
|
||||
// Remove the hash if it exists
|
||||
const color = hexColor.replace(/^#/, ``);
|
||||
|
||||
// Convert hex to RGB
|
||||
const r = parseInt(color.substring(0, 2), 16);
|
||||
const g = parseInt(color.substring(2, 2), 16);
|
||||
const b = parseInt(color.substring(4, 2), 16);
|
||||
|
||||
// Calculate complementary color (inverting the RGB values)
|
||||
const compR = 255 - r;
|
||||
const compG = 255 - g;
|
||||
const compB = 255 - b;
|
||||
|
||||
// Calculate brightness of the background
|
||||
const brightness = r * 0.299 + g * 0.587 + b * 0.114;
|
||||
|
||||
// If the complementary color doesn't have enough contrast, adjust it
|
||||
const compBrightness = compR * 0.299 + compG * 0.587 + compB * 0.114;
|
||||
const brightnessDiff = Math.abs(brightness - compBrightness);
|
||||
|
||||
if (brightnessDiff < 128) {
|
||||
// Not enough contrast, use a more vibrant alternative
|
||||
if (brightness > 128) {
|
||||
// Dark color for light background
|
||||
return `#8e44ad`; // Purple
|
||||
} else {
|
||||
// Light color for dark background
|
||||
return `#f1c40f`; // Yellow
|
||||
}
|
||||
}
|
||||
|
||||
// Convert back to hex
|
||||
return `#${((1 << 24) + (compR << 16) + (compG << 8) + compB).toString(16).slice(1)}`;
|
||||
}
|
||||
@@ -3,3 +3,6 @@ backups/
|
||||
data/
|
||||
secrets/
|
||||
uploads/
|
||||
|
||||
trailbase.js
|
||||
trailbase.d.ts
|
||||
|
||||
@@ -1,33 +1,24 @@
|
||||
# Auto-generated config.Config textproto
|
||||
email {
|
||||
user_verification_template {
|
||||
subject: "Verify your Email Address for {{ APP_NAME }}"
|
||||
body: "<html>\n <body>\n <h1>Welcome {{ EMAIL }}</h1>\n\n <p>\n Thanks for joining {{ APP_NAME }}.\n </p>\n\n <p>\n To be able to log in, first validate your email by clicking the link below.\n </p>\n\n <a class=\"btn\" href=\"{{ VERIFICATION_URL }}\">\n {{ VERIFICATION_URL }}\n </a>\n </body>\n</html>"
|
||||
}
|
||||
password_reset_template {
|
||||
subject: "Reset your Password for {{ APP_NAME }}"
|
||||
body: "<html>\n <body>\n <h1>Password Reset</h1>\n\n <p>\n Click the link below to reset your password.\n </p>\n\n <a class=\"btn\" href=\"{{ VERIFICATION_URL }}\">\n {{ VERIFICATION_URL }}\n </a>\n </body>\n</html>"
|
||||
}
|
||||
change_email_template {
|
||||
subject: "Change your Email Address for {{ APP_NAME }}"
|
||||
body: "<html>\n <body>\n <h1>Change E-Mail Address</h1>\n\n <p>\n Click the link below to verify your new E-mail address:\n </p>\n\n <a class=\"btn\" href=\"{{ VERIFICATION_URL }}\">\n {{ VERIFICATION_URL }}\n </a>\n </body>\n</html>"
|
||||
}
|
||||
}
|
||||
email {}
|
||||
server {
|
||||
application_name: "TrailBase"
|
||||
application_name: "TanStack-DB TrailBase Example"
|
||||
logs_retention_sec: 604800
|
||||
}
|
||||
auth {
|
||||
auth_token_ttl_sec: 120
|
||||
auth_token_ttl_sec: 3600
|
||||
refresh_token_ttl_sec: 2592000
|
||||
}
|
||||
jobs {}
|
||||
record_apis: [
|
||||
{
|
||||
name: "data"
|
||||
table_name: "data"
|
||||
conflict_resolution: REPLACE
|
||||
enable_subscriptions: true
|
||||
name: "todos"
|
||||
table_name: "todos"
|
||||
acl_world: [CREATE, READ, UPDATE, DELETE]
|
||||
enable_subscriptions: true
|
||||
},
|
||||
{
|
||||
name: "config"
|
||||
table_name: "config"
|
||||
acl_world: [CREATE, READ, UPDATE, DELETE]
|
||||
enable_subscriptions: true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
INSERT INTO _user
|
||||
(id, email, password_hash, verified, admin)
|
||||
VALUES
|
||||
(uuid_v7(), 'admin@localhost', (hash_password('secret')), TRUE, TRUE);
|
||||
@@ -1,12 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS data (
|
||||
id INTEGER PRIMARY KEY,
|
||||
updated INTEGER DEFAULT (UNIXEPOCH()) NOT NULL,
|
||||
data TEXT NOT NULL
|
||||
) STRICT;
|
||||
|
||||
CREATE TRIGGER __data__updated_trigger AFTER UPDATE ON data FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE data SET updated = UNIXEPOCH() WHERE id = OLD.id;
|
||||
END;
|
||||
|
||||
INSERT INTO data (data) VALUES ('0'), ('1');
|
||||
@@ -0,0 +1,5 @@
|
||||
-- Create default admin user with top "secret" password.
|
||||
INSERT INTO _user
|
||||
(email, password_hash, verified, admin)
|
||||
VALUES
|
||||
('admin@localhost', (hash_password('secret')), TRUE, TRUE);
|
||||
@@ -0,0 +1,12 @@
|
||||
CREATE TABLE todos (
|
||||
"id" INTEGER PRIMARY KEY NOT NULL,
|
||||
"text" TEXT NOT NULL,
|
||||
"completed" INTEGER NOT NULL DEFAULT 0,
|
||||
"created_at" INTEGER NOT NULL DEFAULT(UNIXEPOCH()),
|
||||
"updated_at" INTEGER NOT NULL DEFAULT(UNIXEPOCH())
|
||||
) STRICT;
|
||||
|
||||
CREATE TRIGGER _todos__update_trigger AFTER UPDATE ON todos FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE todos SET updated_at = UNIXEPOCH() WHERE id = OLD.id;
|
||||
END;
|
||||
@@ -0,0 +1,17 @@
|
||||
CREATE TABLE config (
|
||||
"id" INTEGER PRIMARY KEY NOT NULL,
|
||||
"key" TEXT NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"created_at" INTEGER NOT NULL DEFAULT(UNIXEPOCH()),
|
||||
"updated_at" INTEGER NOT NULL DEFAULT(UNIXEPOCH())
|
||||
) STRICT;
|
||||
|
||||
CREATE UNIQUE INDEX _config_key_index ON config ("key");
|
||||
|
||||
CREATE TRIGGER _config__update_trigger AFTER UPDATE ON config FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE config SET updated_at = UNIXEPOCH() WHERE id = OLD.id;
|
||||
END;
|
||||
|
||||
-- Insert default config for background color
|
||||
INSERT INTO config ("key", "value") VALUES ('backgroundColor', '#f5f5f5');
|
||||
-899
@@ -1,899 +0,0 @@
|
||||
export declare function addCronCallback(name: string, schedule: string, cb: () => void | Promise<void>): void;
|
||||
|
||||
export declare function addPeriodicCallback(milliseconds: number, cb: (cancel: () => void) => void): () => void;
|
||||
|
||||
export declare function addRoute(method: Method, route: string, callback: CallbackType): void;
|
||||
|
||||
export declare type CallbackType = (req: RequestType) => MaybeResponse<ResponseType_2>;
|
||||
|
||||
declare namespace Deno_2 {
|
||||
interface ReadFileOptions {
|
||||
/**
|
||||
* An abort signal to allow cancellation of the file read operation.
|
||||
* If the signal becomes aborted the readFile operation will be stopped
|
||||
* and the promise returned will be rejected with an AbortError.
|
||||
*/
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
interface WriteFileOptions {
|
||||
/** If set to `true`, will append to a file instead of overwriting previous
|
||||
* contents.
|
||||
*
|
||||
* @default {false} */
|
||||
append?: boolean;
|
||||
/** Sets the option to allow creating a new file, if one doesn't already
|
||||
* exist at the specified path.
|
||||
*
|
||||
* @default {true} */
|
||||
create?: boolean;
|
||||
/** If set to `true`, no file, directory, or symlink is allowed to exist at
|
||||
* the target location. When createNew is set to `true`, `create` is ignored.
|
||||
*
|
||||
* @default {false} */
|
||||
createNew?: boolean;
|
||||
/** Permissions always applied to file. */
|
||||
mode?: number;
|
||||
/** An abort signal to allow cancellation of the file write operation.
|
||||
*
|
||||
* If the signal becomes aborted the write file operation will be stopped
|
||||
* and the promise returned will be rejected with an {@linkcode AbortError}.
|
||||
*/
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
/**
|
||||
* Options which can be set when using {@linkcode Deno.makeTempDir},
|
||||
* {@linkcode Deno.makeTempDirSync}, {@linkcode Deno.makeTempFile}, and
|
||||
* {@linkcode Deno.makeTempFileSync}.
|
||||
*
|
||||
* @category File System */
|
||||
interface MakeTempOptions {
|
||||
/** Directory where the temporary directory should be created (defaults to
|
||||
* the env variable `TMPDIR`, or the system's default, usually `/tmp`).
|
||||
*
|
||||
* Note that if the passed `dir` is relative, the path returned by
|
||||
* `makeTempFile()` and `makeTempDir()` will also be relative. Be mindful of
|
||||
* this when changing working directory. */
|
||||
dir?: string;
|
||||
/** String that should precede the random portion of the temporary
|
||||
* directory's name. */
|
||||
prefix?: string;
|
||||
/** String that should follow the random portion of the temporary
|
||||
* directory's name. */
|
||||
suffix?: string;
|
||||
}
|
||||
/**
|
||||
* Options which can be set when using {@linkcode Deno.mkdir} and
|
||||
* {@linkcode Deno.mkdirSync}.
|
||||
*
|
||||
* @category File System */
|
||||
interface MkdirOptions {
|
||||
/** If set to `true`, means that any intermediate directories will also be
|
||||
* created (as with the shell command `mkdir -p`).
|
||||
*
|
||||
* Intermediate directories are created with the same permissions.
|
||||
*
|
||||
* When recursive is set to `true`, succeeds silently (without changing any
|
||||
* permissions) if a directory already exists at the path, or if the path
|
||||
* is a symlink to an existing directory.
|
||||
*
|
||||
* @default {false} */
|
||||
recursive?: boolean;
|
||||
/** Permissions to use when creating the directory (defaults to `0o777`,
|
||||
* before the process's umask).
|
||||
*
|
||||
* Ignored on Windows. */
|
||||
mode?: number;
|
||||
}
|
||||
/**
|
||||
* Information about a directory entry returned from {@linkcode Deno.readDir}
|
||||
* and {@linkcode Deno.readDirSync}.
|
||||
*
|
||||
* @category File System */
|
||||
interface DirEntry {
|
||||
/** The file name of the entry. It is just the entity name and does not
|
||||
* include the full path. */
|
||||
name: string;
|
||||
/** True if this is info for a regular file. Mutually exclusive to
|
||||
* `DirEntry.isDirectory` and `DirEntry.isSymlink`. */
|
||||
isFile: boolean;
|
||||
/** True if this is info for a regular directory. Mutually exclusive to
|
||||
* `DirEntry.isFile` and `DirEntry.isSymlink`. */
|
||||
isDirectory: boolean;
|
||||
/** True if this is info for a symlink. Mutually exclusive to
|
||||
* `DirEntry.isFile` and `DirEntry.isDirectory`. */
|
||||
isSymlink: boolean;
|
||||
}
|
||||
/**
|
||||
* Options which can be set when doing {@linkcode Deno.open} and
|
||||
* {@linkcode Deno.openSync}.
|
||||
*
|
||||
* @category File System */
|
||||
interface OpenOptions {
|
||||
/** Sets the option for read access. This option, when `true`, means that
|
||||
* the file should be read-able if opened.
|
||||
*
|
||||
* @default {true} */
|
||||
read?: boolean;
|
||||
/** Sets the option for write access. This option, when `true`, means that
|
||||
* the file should be write-able if opened. If the file already exists,
|
||||
* any write calls on it will overwrite its contents, by default without
|
||||
* truncating it.
|
||||
*
|
||||
* @default {false} */
|
||||
write?: boolean;
|
||||
/** Sets the option for the append mode. This option, when `true`, means
|
||||
* that writes will append to a file instead of overwriting previous
|
||||
* contents.
|
||||
*
|
||||
* Note that setting `{ write: true, append: true }` has the same effect as
|
||||
* setting only `{ append: true }`.
|
||||
*
|
||||
* @default {false} */
|
||||
append?: boolean;
|
||||
/** Sets the option for truncating a previous file. If a file is
|
||||
* successfully opened with this option set it will truncate the file to `0`
|
||||
* size if it already exists. The file must be opened with write access
|
||||
* for truncate to work.
|
||||
*
|
||||
* @default {false} */
|
||||
truncate?: boolean;
|
||||
/** Sets the option to allow creating a new file, if one doesn't already
|
||||
* exist at the specified path. Requires write or append access to be
|
||||
* used.
|
||||
*
|
||||
* @default {false} */
|
||||
create?: boolean;
|
||||
/** If set to `true`, no file, directory, or symlink is allowed to exist at
|
||||
* the target location. Requires write or append access to be used. When
|
||||
* createNew is set to `true`, create and truncate are ignored.
|
||||
*
|
||||
* @default {false} */
|
||||
createNew?: boolean;
|
||||
/** Permissions to use if creating the file (defaults to `0o666`, before
|
||||
* the process's umask).
|
||||
*
|
||||
* Ignored on Windows. */
|
||||
mode?: number;
|
||||
}
|
||||
/**
|
||||
* Options which can be set when using {@linkcode Deno.remove} and
|
||||
* {@linkcode Deno.removeSync}.
|
||||
*
|
||||
* @category File System */
|
||||
interface RemoveOptions {
|
||||
/** If set to `true`, path will be removed even if it's a non-empty directory.
|
||||
*
|
||||
* @default {false} */
|
||||
recursive?: boolean;
|
||||
}
|
||||
/** Options that can be used with {@linkcode symlink} and
|
||||
* {@linkcode symlinkSync}.
|
||||
*
|
||||
* @category File System */
|
||||
interface SymlinkOptions {
|
||||
/** Specify the symbolic link type as file, directory or NTFS junction. This
|
||||
* option only applies to Windows and is ignored on other operating systems. */
|
||||
type: "file" | "dir" | "junction";
|
||||
}
|
||||
function writeFile(path: string | URL, data: Uint8Array | ReadableStream<Uint8Array>, options?: WriteFileOptions): Promise<void>;
|
||||
function writeTextFile(path: string | URL, data: string | ReadableStream<string>, options?: WriteFileOptions): Promise<void>;
|
||||
function readTextFile(path: string | URL, options?: ReadFileOptions): Promise<string>;
|
||||
function readFile(path: string | URL, options?: ReadFileOptions): Promise<Uint8Array>;
|
||||
function chmod(path: string | URL, mode: number): Promise<void>;
|
||||
function chown(path: string | URL, uid: number | null, gid: number | null): Promise<void>;
|
||||
function cwd(): string;
|
||||
function makeTempDir(options?: MakeTempOptions): Promise<string>;
|
||||
function makeTempFile(options?: MakeTempOptions): Promise<string>;
|
||||
function mkdir(path: string | URL, options?: MkdirOptions): Promise<void>;
|
||||
function chdir(directory: string | URL): void;
|
||||
function copyFile(fromPath: string | URL, toPath: string | URL): Promise<void>;
|
||||
function readDir(path: string | URL): AsyncIterable<DirEntry>;
|
||||
function readLink(path: string | URL): Promise<string>;
|
||||
function realPath(path: string | URL): Promise<string>;
|
||||
function remove(path: string | URL, options?: RemoveOptions): Promise<void>;
|
||||
function rename(oldpath: string | URL, newpath: string | URL): Promise<void>;
|
||||
function stat(path: string | URL): Promise<FileInfo>;
|
||||
function lstat(path: string | URL): Promise<FileInfo>;
|
||||
function truncate(name: string, len?: number): Promise<void>;
|
||||
function open(path: string | URL, options?: OpenOptions): Promise<FsFile>;
|
||||
function create(path: string | URL): Promise<FsFile>;
|
||||
function symlink(oldpath: string | URL, newpath: string | URL, options?: SymlinkOptions): Promise<void>;
|
||||
function link(oldpath: string, newpath: string): Promise<void>;
|
||||
function utime(path: string | URL, atime: number | Date, mtime: number | Date): Promise<void>;
|
||||
function umask(mask?: number): number;
|
||||
/** Provides information about a file and is returned by
|
||||
* {@linkcode Deno.stat}, {@linkcode Deno.lstat}, {@linkcode Deno.statSync},
|
||||
* and {@linkcode Deno.lstatSync} or from calling `stat()` and `statSync()`
|
||||
* on an {@linkcode Deno.FsFile} instance.
|
||||
*
|
||||
* @category File System
|
||||
*/
|
||||
interface FileInfo {
|
||||
/** True if this is info for a regular file. Mutually exclusive to
|
||||
* `FileInfo.isDirectory` and `FileInfo.isSymlink`. */
|
||||
isFile: boolean;
|
||||
/** True if this is info for a regular directory. Mutually exclusive to
|
||||
* `FileInfo.isFile` and `FileInfo.isSymlink`. */
|
||||
isDirectory: boolean;
|
||||
/** True if this is info for a symlink. Mutually exclusive to
|
||||
* `FileInfo.isFile` and `FileInfo.isDirectory`. */
|
||||
isSymlink: boolean;
|
||||
/** The size of the file, in bytes. */
|
||||
size: number;
|
||||
/** The last modification time of the file. This corresponds to the `mtime`
|
||||
* field from `stat` on Linux/Mac OS and `ftLastWriteTime` on Windows. This
|
||||
* may not be available on all platforms. */
|
||||
mtime: Date | null;
|
||||
/** The last access time of the file. This corresponds to the `atime`
|
||||
* field from `stat` on Unix and `ftLastAccessTime` on Windows. This may not
|
||||
* be available on all platforms. */
|
||||
atime: Date | null;
|
||||
/** The creation time of the file. This corresponds to the `birthtime`
|
||||
* field from `stat` on Mac/BSD and `ftCreationTime` on Windows. This may
|
||||
* not be available on all platforms. */
|
||||
birthtime: Date | null;
|
||||
/** The last change time of the file. This corresponds to the `ctime`
|
||||
* field from `stat` on Mac/BSD and `ChangeTime` on Windows. This may
|
||||
* not be available on all platforms. */
|
||||
ctime: Date | null;
|
||||
/** ID of the device containing the file. */
|
||||
dev: number;
|
||||
/** Inode number.
|
||||
*
|
||||
* _Linux/Mac OS only._ */
|
||||
ino: number | null;
|
||||
/** The underlying raw `st_mode` bits that contain the standard Unix
|
||||
* permissions for this file/directory.
|
||||
*/
|
||||
mode: number | null;
|
||||
/** Number of hard links pointing to this file.
|
||||
*
|
||||
* _Linux/Mac OS only._ */
|
||||
nlink: number | null;
|
||||
/** User ID of the owner of this file.
|
||||
*
|
||||
* _Linux/Mac OS only._ */
|
||||
uid: number | null;
|
||||
/** Group ID of the owner of this file.
|
||||
*
|
||||
* _Linux/Mac OS only._ */
|
||||
gid: number | null;
|
||||
/** Device ID of this file.
|
||||
*
|
||||
* _Linux/Mac OS only._ */
|
||||
rdev: number | null;
|
||||
/** Blocksize for filesystem I/O.
|
||||
*
|
||||
* _Linux/Mac OS only._ */
|
||||
blksize: number | null;
|
||||
/** Number of blocks allocated to the file, in 512-byte units.
|
||||
*
|
||||
* _Linux/Mac OS only._ */
|
||||
blocks: number | null;
|
||||
/** True if this is info for a block device.
|
||||
*
|
||||
* _Linux/Mac OS only._ */
|
||||
isBlockDevice: boolean | null;
|
||||
/** True if this is info for a char device.
|
||||
*
|
||||
* _Linux/Mac OS only._ */
|
||||
isCharDevice: boolean | null;
|
||||
/** True if this is info for a fifo.
|
||||
*
|
||||
* _Linux/Mac OS only._ */
|
||||
isFifo: boolean | null;
|
||||
/** True if this is info for a socket.
|
||||
*
|
||||
* _Linux/Mac OS only._ */
|
||||
isSocket: boolean | null;
|
||||
}
|
||||
/**
|
||||
* A enum which defines the seek mode for IO related APIs that support
|
||||
* seeking.
|
||||
*
|
||||
* @category I/O */
|
||||
enum SeekMode {
|
||||
Start = 0,
|
||||
Current = 1,
|
||||
End = 2
|
||||
}
|
||||
/** @category I/O */
|
||||
interface SetRawOptions {
|
||||
/**
|
||||
* The `cbreak` option can be used to indicate that characters that
|
||||
* correspond to a signal should still be generated. When disabling raw
|
||||
* mode, this option is ignored. This functionality currently only works on
|
||||
* Linux and Mac OS.
|
||||
*/
|
||||
cbreak: boolean;
|
||||
}
|
||||
class FsFile implements Disposable {
|
||||
/** A {@linkcode ReadableStream} instance representing to the byte contents
|
||||
* of the file. This makes it easy to interoperate with other web streams
|
||||
* based APIs.
|
||||
*
|
||||
* ```ts
|
||||
* using file = await Deno.open("my_file.txt", { read: true });
|
||||
* const decoder = new TextDecoder();
|
||||
* for await (const chunk of file.readable) {
|
||||
* console.log(decoder.decode(chunk));
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
readonly readable: ReadableStream<Uint8Array>;
|
||||
/** A {@linkcode WritableStream} instance to write the contents of the
|
||||
* file. This makes it easy to interoperate with other web streams based
|
||||
* APIs.
|
||||
*
|
||||
* ```ts
|
||||
* const items = ["hello", "world"];
|
||||
* using file = await Deno.open("my_file.txt", { write: true });
|
||||
* const encoder = new TextEncoder();
|
||||
* const writer = file.writable.getWriter();
|
||||
* for (const item of items) {
|
||||
* await writer.write(encoder.encode(item));
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
readonly writable: WritableStream<Uint8Array>;
|
||||
/** Write the contents of the array buffer (`p`) to the file.
|
||||
*
|
||||
* Resolves to the number of bytes written.
|
||||
*
|
||||
* **It is not guaranteed that the full buffer will be written in a single
|
||||
* call.**
|
||||
*
|
||||
* ```ts
|
||||
* const encoder = new TextEncoder();
|
||||
* const data = encoder.encode("Hello world");
|
||||
* using file = await Deno.open("/foo/bar.txt", { write: true });
|
||||
* const bytesWritten = await file.write(data); // 11
|
||||
* ```
|
||||
*
|
||||
* @category I/O
|
||||
*/
|
||||
write(p: Uint8Array): Promise<number>;
|
||||
/** Synchronously write the contents of the array buffer (`p`) to the file.
|
||||
*
|
||||
* Returns the number of bytes written.
|
||||
*
|
||||
* **It is not guaranteed that the full buffer will be written in a single
|
||||
* call.**
|
||||
*
|
||||
* ```ts
|
||||
* const encoder = new TextEncoder();
|
||||
* const data = encoder.encode("Hello world");
|
||||
* using file = Deno.openSync("/foo/bar.txt", { write: true });
|
||||
* const bytesWritten = file.writeSync(data); // 11
|
||||
* ```
|
||||
*/
|
||||
writeSync(p: Uint8Array): number;
|
||||
/** Truncates (or extends) the file to reach the specified `len`. If `len`
|
||||
* is not specified, then the entire file contents are truncated.
|
||||
*
|
||||
* ### Truncate the entire file
|
||||
*
|
||||
* ```ts
|
||||
* using file = await Deno.open("my_file.txt", { write: true });
|
||||
* await file.truncate();
|
||||
* ```
|
||||
*
|
||||
* ### Truncate part of the file
|
||||
*
|
||||
* ```ts
|
||||
* // if "my_file.txt" contains the text "hello world":
|
||||
* using file = await Deno.open("my_file.txt", { write: true });
|
||||
* await file.truncate(7);
|
||||
* const buf = new Uint8Array(100);
|
||||
* await file.read(buf);
|
||||
* const text = new TextDecoder().decode(buf); // "hello w"
|
||||
* ```
|
||||
*/
|
||||
truncate(len?: number): Promise<void>;
|
||||
/** Synchronously truncates (or extends) the file to reach the specified
|
||||
* `len`. If `len` is not specified, then the entire file contents are
|
||||
* truncated.
|
||||
*
|
||||
* ### Truncate the entire file
|
||||
*
|
||||
* ```ts
|
||||
* using file = Deno.openSync("my_file.txt", { write: true });
|
||||
* file.truncateSync();
|
||||
* ```
|
||||
*
|
||||
* ### Truncate part of the file
|
||||
*
|
||||
* ```ts
|
||||
* // if "my_file.txt" contains the text "hello world":
|
||||
* using file = Deno.openSync("my_file.txt", { write: true });
|
||||
* file.truncateSync(7);
|
||||
* const buf = new Uint8Array(100);
|
||||
* file.readSync(buf);
|
||||
* const text = new TextDecoder().decode(buf); // "hello w"
|
||||
* ```
|
||||
*/
|
||||
truncateSync(len?: number): void;
|
||||
/** Read the file into an array buffer (`p`).
|
||||
*
|
||||
* Resolves to either the number of bytes read during the operation or EOF
|
||||
* (`null`) if there was nothing more to read.
|
||||
*
|
||||
* It is possible for a read to successfully return with `0` bytes. This
|
||||
* does not indicate EOF.
|
||||
*
|
||||
* **It is not guaranteed that the full buffer will be read in a single
|
||||
* call.**
|
||||
*
|
||||
* ```ts
|
||||
* // if "/foo/bar.txt" contains the text "hello world":
|
||||
* using file = await Deno.open("/foo/bar.txt");
|
||||
* const buf = new Uint8Array(100);
|
||||
* const numberOfBytesRead = await file.read(buf); // 11 bytes
|
||||
* const text = new TextDecoder().decode(buf); // "hello world"
|
||||
* ```
|
||||
*/
|
||||
read(p: Uint8Array): Promise<number | null>;
|
||||
/** Synchronously read from the file into an array buffer (`p`).
|
||||
*
|
||||
* Returns either the number of bytes read during the operation or EOF
|
||||
* (`null`) if there was nothing more to read.
|
||||
*
|
||||
* It is possible for a read to successfully return with `0` bytes. This
|
||||
* does not indicate EOF.
|
||||
*
|
||||
* **It is not guaranteed that the full buffer will be read in a single
|
||||
* call.**
|
||||
*
|
||||
* ```ts
|
||||
* // if "/foo/bar.txt" contains the text "hello world":
|
||||
* using file = Deno.openSync("/foo/bar.txt");
|
||||
* const buf = new Uint8Array(100);
|
||||
* const numberOfBytesRead = file.readSync(buf); // 11 bytes
|
||||
* const text = new TextDecoder().decode(buf); // "hello world"
|
||||
* ```
|
||||
*/
|
||||
readSync(p: Uint8Array): number | null;
|
||||
/** Seek to the given `offset` under mode given by `whence`. The call
|
||||
* resolves to the new position within the resource (bytes from the start).
|
||||
*
|
||||
* ```ts
|
||||
* // Given the file contains "Hello world" text, which is 11 bytes long:
|
||||
* using file = await Deno.open(
|
||||
* "hello.txt",
|
||||
* { read: true, write: true, truncate: true, create: true },
|
||||
* );
|
||||
* await file.write(new TextEncoder().encode("Hello world"));
|
||||
*
|
||||
* // advance cursor 6 bytes
|
||||
* const cursorPosition = await file.seek(6, Deno.SeekMode.Start);
|
||||
* console.log(cursorPosition); // 6
|
||||
* const buf = new Uint8Array(100);
|
||||
* await file.read(buf);
|
||||
* console.log(new TextDecoder().decode(buf)); // "world"
|
||||
* ```
|
||||
*
|
||||
* The seek modes work as follows:
|
||||
*
|
||||
* ```ts
|
||||
* // Given the file contains "Hello world" text, which is 11 bytes long:
|
||||
* const file = await Deno.open(
|
||||
* "hello.txt",
|
||||
* { read: true, write: true, truncate: true, create: true },
|
||||
* );
|
||||
* await file.write(new TextEncoder().encode("Hello world"));
|
||||
*
|
||||
* // Seek 6 bytes from the start of the file
|
||||
* console.log(await file.seek(6, Deno.SeekMode.Start)); // "6"
|
||||
* // Seek 2 more bytes from the current position
|
||||
* console.log(await file.seek(2, Deno.SeekMode.Current)); // "8"
|
||||
* // Seek backwards 2 bytes from the end of the file
|
||||
* console.log(await file.seek(-2, Deno.SeekMode.End)); // "9" (i.e. 11-2)
|
||||
* ```
|
||||
*/
|
||||
seek(offset: number | bigint, whence: SeekMode): Promise<number>;
|
||||
/** Synchronously seek to the given `offset` under mode given by `whence`.
|
||||
* The new position within the resource (bytes from the start) is returned.
|
||||
*
|
||||
* ```ts
|
||||
* using file = Deno.openSync(
|
||||
* "hello.txt",
|
||||
* { read: true, write: true, truncate: true, create: true },
|
||||
* );
|
||||
* file.writeSync(new TextEncoder().encode("Hello world"));
|
||||
*
|
||||
* // advance cursor 6 bytes
|
||||
* const cursorPosition = file.seekSync(6, Deno.SeekMode.Start);
|
||||
* console.log(cursorPosition); // 6
|
||||
* const buf = new Uint8Array(100);
|
||||
* file.readSync(buf);
|
||||
* console.log(new TextDecoder().decode(buf)); // "world"
|
||||
* ```
|
||||
*
|
||||
* The seek modes work as follows:
|
||||
*
|
||||
* ```ts
|
||||
* // Given the file contains "Hello world" text, which is 11 bytes long:
|
||||
* using file = Deno.openSync(
|
||||
* "hello.txt",
|
||||
* { read: true, write: true, truncate: true, create: true },
|
||||
* );
|
||||
* file.writeSync(new TextEncoder().encode("Hello world"));
|
||||
*
|
||||
* // Seek 6 bytes from the start of the file
|
||||
* console.log(file.seekSync(6, Deno.SeekMode.Start)); // "6"
|
||||
* // Seek 2 more bytes from the current position
|
||||
* console.log(file.seekSync(2, Deno.SeekMode.Current)); // "8"
|
||||
* // Seek backwards 2 bytes from the end of the file
|
||||
* console.log(file.seekSync(-2, Deno.SeekMode.End)); // "9" (i.e. 11-2)
|
||||
* ```
|
||||
*/
|
||||
seekSync(offset: number | bigint, whence: SeekMode): number;
|
||||
/** Resolves to a {@linkcode Deno.FileInfo} for the file.
|
||||
*
|
||||
* ```ts
|
||||
* import { assert } from "jsr:@std/assert";
|
||||
*
|
||||
* using file = await Deno.open("hello.txt");
|
||||
* const fileInfo = await file.stat();
|
||||
* assert(fileInfo.isFile);
|
||||
* ```
|
||||
*/
|
||||
stat(): Promise<FileInfo>;
|
||||
/** Synchronously returns a {@linkcode Deno.FileInfo} for the file.
|
||||
*
|
||||
* ```ts
|
||||
* import { assert } from "jsr:@std/assert";
|
||||
*
|
||||
* using file = Deno.openSync("hello.txt")
|
||||
* const fileInfo = file.statSync();
|
||||
* assert(fileInfo.isFile);
|
||||
* ```
|
||||
*/
|
||||
statSync(): FileInfo;
|
||||
/**
|
||||
* Flushes any pending data and metadata operations of the given file
|
||||
* stream to disk.
|
||||
*
|
||||
* ```ts
|
||||
* const file = await Deno.open(
|
||||
* "my_file.txt",
|
||||
* { read: true, write: true, create: true },
|
||||
* );
|
||||
* await file.write(new TextEncoder().encode("Hello World"));
|
||||
* await file.truncate(1);
|
||||
* await file.sync();
|
||||
* console.log(await Deno.readTextFile("my_file.txt")); // H
|
||||
* ```
|
||||
*
|
||||
* @category I/O
|
||||
*/
|
||||
sync(): Promise<void>;
|
||||
/**
|
||||
* Synchronously flushes any pending data and metadata operations of the given
|
||||
* file stream to disk.
|
||||
*
|
||||
* ```ts
|
||||
* const file = Deno.openSync(
|
||||
* "my_file.txt",
|
||||
* { read: true, write: true, create: true },
|
||||
* );
|
||||
* file.writeSync(new TextEncoder().encode("Hello World"));
|
||||
* file.truncateSync(1);
|
||||
* file.syncSync();
|
||||
* console.log(Deno.readTextFileSync("my_file.txt")); // H
|
||||
* ```
|
||||
*
|
||||
* @category I/O
|
||||
*/
|
||||
syncSync(): void;
|
||||
/**
|
||||
* Flushes any pending data operations of the given file stream to disk.
|
||||
* ```ts
|
||||
* using file = await Deno.open(
|
||||
* "my_file.txt",
|
||||
* { read: true, write: true, create: true },
|
||||
* );
|
||||
* await file.write(new TextEncoder().encode("Hello World"));
|
||||
* await file.syncData();
|
||||
* console.log(await Deno.readTextFile("my_file.txt")); // Hello World
|
||||
* ```
|
||||
*
|
||||
* @category I/O
|
||||
*/
|
||||
syncData(): Promise<void>;
|
||||
/**
|
||||
* Synchronously flushes any pending data operations of the given file stream
|
||||
* to disk.
|
||||
*
|
||||
* ```ts
|
||||
* using file = Deno.openSync(
|
||||
* "my_file.txt",
|
||||
* { read: true, write: true, create: true },
|
||||
* );
|
||||
* file.writeSync(new TextEncoder().encode("Hello World"));
|
||||
* file.syncDataSync();
|
||||
* console.log(Deno.readTextFileSync("my_file.txt")); // Hello World
|
||||
* ```
|
||||
*
|
||||
* @category I/O
|
||||
*/
|
||||
syncDataSync(): void;
|
||||
/**
|
||||
* Changes the access (`atime`) and modification (`mtime`) times of the
|
||||
* file stream resource. Given times are either in seconds (UNIX epoch
|
||||
* time) or as `Date` objects.
|
||||
*
|
||||
* ```ts
|
||||
* using file = await Deno.open("file.txt", { create: true, write: true });
|
||||
* await file.utime(1556495550, new Date());
|
||||
* ```
|
||||
*
|
||||
* @category File System
|
||||
*/
|
||||
utime(atime: number | Date, mtime: number | Date): Promise<void>;
|
||||
/**
|
||||
* Synchronously changes the access (`atime`) and modification (`mtime`)
|
||||
* times of the file stream resource. Given times are either in seconds
|
||||
* (UNIX epoch time) or as `Date` objects.
|
||||
*
|
||||
* ```ts
|
||||
* using file = Deno.openSync("file.txt", { create: true, write: true });
|
||||
* file.utime(1556495550, new Date());
|
||||
* ```
|
||||
*
|
||||
* @category File System
|
||||
*/
|
||||
utimeSync(atime: number | Date, mtime: number | Date): void;
|
||||
/** **UNSTABLE**: New API, yet to be vetted.
|
||||
*
|
||||
* Checks if the file resource is a TTY (terminal).
|
||||
*
|
||||
* ```ts
|
||||
* // This example is system and context specific
|
||||
* using file = await Deno.open("/dev/tty6");
|
||||
* file.isTerminal(); // true
|
||||
* ```
|
||||
*/
|
||||
isTerminal(): boolean;
|
||||
/** **UNSTABLE**: New API, yet to be vetted.
|
||||
*
|
||||
* Set TTY to be under raw mode or not. In raw mode, characters are read and
|
||||
* returned as is, without being processed. All special processing of
|
||||
* characters by the terminal is disabled, including echoing input
|
||||
* characters. Reading from a TTY device in raw mode is faster than reading
|
||||
* from a TTY device in canonical mode.
|
||||
*
|
||||
* ```ts
|
||||
* using file = await Deno.open("/dev/tty6");
|
||||
* file.setRaw(true, { cbreak: true });
|
||||
* ```
|
||||
*/
|
||||
setRaw(mode: boolean, options?: SetRawOptions): void;
|
||||
/**
|
||||
* Acquire an advisory file-system lock for the file.
|
||||
*
|
||||
* @param [exclusive=false]
|
||||
*/
|
||||
lock(exclusive?: boolean): Promise<void>;
|
||||
/**
|
||||
* Synchronously acquire an advisory file-system lock synchronously for the file.
|
||||
*
|
||||
* @param [exclusive=false]
|
||||
*/
|
||||
lockSync(exclusive?: boolean): void;
|
||||
/**
|
||||
* Release an advisory file-system lock for the file.
|
||||
*/
|
||||
unlock(): Promise<void>;
|
||||
/**
|
||||
* Synchronously release an advisory file-system lock for the file.
|
||||
*/
|
||||
unlockSync(): void;
|
||||
/** Close the file. Closing a file when you are finished with it is
|
||||
* important to avoid leaking resources.
|
||||
*
|
||||
* ```ts
|
||||
* using file = await Deno.open("my_file.txt");
|
||||
* // do work with "file" object
|
||||
* ```
|
||||
*/
|
||||
close(): void;
|
||||
[Symbol.dispose](): void;
|
||||
}
|
||||
}
|
||||
|
||||
export declare function execute(sql: string, params: unknown[]): Promise<number>;
|
||||
|
||||
export declare namespace fs {
|
||||
const writeFile: typeof Deno_2.writeFile;
|
||||
const writeTextFile: typeof Deno_2.writeTextFile;
|
||||
const readTextFile: typeof Deno_2.readTextFile;
|
||||
const readFile: typeof Deno_2.readFile;
|
||||
const chmod: typeof Deno_2.chmod;
|
||||
const chown: typeof Deno_2.chown;
|
||||
const cwd: typeof Deno_2.cwd;
|
||||
const makeTempDir: typeof Deno_2.makeTempDir;
|
||||
const makeTempFile: typeof Deno_2.makeTempFile;
|
||||
const mkdir: typeof Deno_2.mkdir;
|
||||
const chdir: typeof Deno_2.chdir;
|
||||
const copyFile: typeof Deno_2.copyFile;
|
||||
const readDir: typeof Deno_2.readDir;
|
||||
const readLink: typeof Deno_2.readLink;
|
||||
const realPath: typeof Deno_2.realPath;
|
||||
const remove: typeof Deno_2.remove;
|
||||
const rename: typeof Deno_2.rename;
|
||||
const stat: typeof Deno_2.stat;
|
||||
const lstat: typeof Deno_2.lstat;
|
||||
const truncate: typeof Deno_2.truncate;
|
||||
const FsFile: typeof Deno_2.FsFile;
|
||||
const open: typeof Deno_2.open;
|
||||
const create: typeof Deno_2.create;
|
||||
const symlink: typeof Deno_2.symlink;
|
||||
const link: typeof Deno_2.link;
|
||||
const utime: typeof Deno_2.utime;
|
||||
const umask: typeof Deno_2.umask;
|
||||
}
|
||||
|
||||
export declare type HeaderMapType = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
export declare function htmlHandler(f: (req: StringRequestType) => MaybeResponse<HtmlResponseType | string>): CallbackType;
|
||||
|
||||
export declare type HtmlResponseType = {
|
||||
headers?: [string, string][];
|
||||
status?: number;
|
||||
body: string;
|
||||
};
|
||||
|
||||
export declare class HttpError extends Error {
|
||||
readonly statusCode: number;
|
||||
readonly headers: [string, string][] | undefined;
|
||||
constructor(statusCode: number, message?: string, headers?: [string, string][]);
|
||||
toString(): string;
|
||||
toResponse(): ResponseType_2;
|
||||
}
|
||||
|
||||
export declare function jsonHandler(f: (req: JsonRequestType) => MaybeResponse<JsonRequestType | object>): CallbackType;
|
||||
|
||||
export declare type JsonRequestType = {
|
||||
uri: string;
|
||||
params: PathParamsType;
|
||||
headers: HeaderMapType;
|
||||
user?: UserType;
|
||||
body?: object | string;
|
||||
};
|
||||
|
||||
export declare interface JsonResponseType {
|
||||
headers?: [string, string][];
|
||||
status?: number;
|
||||
body: object;
|
||||
}
|
||||
|
||||
export declare type MaybeResponse<T> = Promise<T | undefined> | T | undefined;
|
||||
|
||||
export declare type Method = "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT" | "TRACE";
|
||||
|
||||
export declare type ParsedPath = {
|
||||
path: string;
|
||||
query: URLSearchParams;
|
||||
};
|
||||
|
||||
export declare function parsePath(path: string): ParsedPath;
|
||||
|
||||
export declare type PathParamsType = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
export declare function query(sql: string, params: unknown[]): Promise<unknown[][]>;
|
||||
|
||||
export declare type RequestType = {
|
||||
uri: string;
|
||||
params: PathParamsType;
|
||||
headers: HeaderMapType;
|
||||
user?: UserType;
|
||||
body?: Uint8Array;
|
||||
};
|
||||
|
||||
declare type ResponseType_2 = {
|
||||
headers?: [string, string][];
|
||||
status?: number;
|
||||
body?: Uint8Array;
|
||||
};
|
||||
export { ResponseType_2 as ResponseType }
|
||||
|
||||
export declare enum StatusCodes {
|
||||
CONTINUE = 100,
|
||||
SWITCHING_PROTOCOLS = 101,
|
||||
PROCESSING = 102,
|
||||
EARLY_HINTS = 103,
|
||||
OK = 200,
|
||||
CREATED = 201,
|
||||
ACCEPTED = 202,
|
||||
NON_AUTHORITATIVE_INFORMATION = 203,
|
||||
NO_CONTENT = 204,
|
||||
RESET_CONTENT = 205,
|
||||
PARTIAL_CONTENT = 206,
|
||||
MULTI_STATUS = 207,
|
||||
MULTIPLE_CHOICES = 300,
|
||||
MOVED_PERMANENTLY = 301,
|
||||
MOVED_TEMPORARILY = 302,
|
||||
SEE_OTHER = 303,
|
||||
NOT_MODIFIED = 304,
|
||||
USE_PROXY = 305,
|
||||
TEMPORARY_REDIRECT = 307,
|
||||
PERMANENT_REDIRECT = 308,
|
||||
BAD_REQUEST = 400,
|
||||
UNAUTHORIZED = 401,
|
||||
PAYMENT_REQUIRED = 402,
|
||||
FORBIDDEN = 403,
|
||||
NOT_FOUND = 404,
|
||||
METHOD_NOT_ALLOWED = 405,
|
||||
NOT_ACCEPTABLE = 406,
|
||||
PROXY_AUTHENTICATION_REQUIRED = 407,
|
||||
REQUEST_TIMEOUT = 408,
|
||||
CONFLICT = 409,
|
||||
GONE = 410,
|
||||
LENGTH_REQUIRED = 411,
|
||||
PRECONDITION_FAILED = 412,
|
||||
REQUEST_TOO_LONG = 413,
|
||||
REQUEST_URI_TOO_LONG = 414,
|
||||
UNSUPPORTED_MEDIA_TYPE = 415,
|
||||
REQUESTED_RANGE_NOT_SATISFIABLE = 416,
|
||||
EXPECTATION_FAILED = 417,
|
||||
IM_A_TEAPOT = 418,
|
||||
INSUFFICIENT_SPACE_ON_RESOURCE = 419,
|
||||
METHOD_FAILURE = 420,
|
||||
MISDIRECTED_REQUEST = 421,
|
||||
UNPROCESSABLE_ENTITY = 422,
|
||||
LOCKED = 423,
|
||||
FAILED_DEPENDENCY = 424,
|
||||
UPGRADE_REQUIRED = 426,
|
||||
PRECONDITION_REQUIRED = 428,
|
||||
TOO_MANY_REQUESTS = 429,
|
||||
REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
|
||||
UNAVAILABLE_FOR_LEGAL_REASONS = 451,
|
||||
INTERNAL_SERVER_ERROR = 500,
|
||||
NOT_IMPLEMENTED = 501,
|
||||
BAD_GATEWAY = 502,
|
||||
SERVICE_UNAVAILABLE = 503,
|
||||
GATEWAY_TIMEOUT = 504,
|
||||
HTTP_VERSION_NOT_SUPPORTED = 505,
|
||||
INSUFFICIENT_STORAGE = 507,
|
||||
NETWORK_AUTHENTICATION_REQUIRED = 511
|
||||
}
|
||||
|
||||
export declare function stringHandler(f: (req: StringRequestType) => MaybeResponse<StringResponseType | string>): CallbackType;
|
||||
|
||||
export declare type StringRequestType = {
|
||||
uri: string;
|
||||
params: PathParamsType;
|
||||
headers: HeaderMapType;
|
||||
user?: UserType;
|
||||
body?: string;
|
||||
};
|
||||
|
||||
export declare type StringResponseType = {
|
||||
headers?: [string, string][];
|
||||
status?: number;
|
||||
body: string;
|
||||
};
|
||||
|
||||
export declare class Transaction {
|
||||
finalized: boolean;
|
||||
constructor();
|
||||
query(queryStr: string, params: unknown[]): unknown[][];
|
||||
execute(queryStr: string, params: unknown[]): number;
|
||||
commit(): void;
|
||||
rollback(): void;
|
||||
}
|
||||
|
||||
export declare function transaction<T>(f: (tx: Transaction) => T): Promise<T>;
|
||||
|
||||
export declare type UserType = {
|
||||
id: string;
|
||||
email: string;
|
||||
csrf: string;
|
||||
};
|
||||
|
||||
export { }
|
||||
@@ -1,460 +0,0 @@
|
||||
var fs;
|
||||
((fs2) => {
|
||||
fs2.writeFile = Deno.writeFile;
|
||||
fs2.writeTextFile = Deno.writeTextFile;
|
||||
fs2.readTextFile = Deno.readTextFile;
|
||||
fs2.readFile = Deno.readFile;
|
||||
fs2.chmod = Deno.chmod;
|
||||
fs2.chown = Deno.chown;
|
||||
fs2.cwd = Deno.cwd;
|
||||
fs2.makeTempDir = Deno.makeTempDir;
|
||||
fs2.makeTempFile = Deno.makeTempFile;
|
||||
fs2.mkdir = Deno.mkdir;
|
||||
fs2.chdir = Deno.chdir;
|
||||
fs2.copyFile = Deno.copyFile;
|
||||
fs2.readDir = Deno.readDir;
|
||||
fs2.readLink = Deno.readLink;
|
||||
fs2.realPath = Deno.realPath;
|
||||
fs2.remove = Deno.remove;
|
||||
fs2.rename = Deno.rename;
|
||||
fs2.stat = Deno.stat;
|
||||
fs2.lstat = Deno.lstat;
|
||||
fs2.truncate = Deno.truncate;
|
||||
fs2.FsFile = Deno.FsFile;
|
||||
fs2.open = Deno.open;
|
||||
fs2.create = Deno.create;
|
||||
fs2.symlink = Deno.symlink;
|
||||
fs2.link = Deno.link;
|
||||
fs2.utime = Deno.utime;
|
||||
fs2.umask = Deno.umask;
|
||||
})(fs || (fs = {}));
|
||||
function decodeFallback(bytes) {
|
||||
var inputIndex = 0;
|
||||
var pendingSize = Math.min(256 * 256, bytes.length + 1);
|
||||
var pending = new Uint16Array(pendingSize);
|
||||
var chunks = [];
|
||||
var pendingIndex = 0;
|
||||
for (; ; ) {
|
||||
var more = inputIndex < bytes.length;
|
||||
if (!more || pendingIndex >= pendingSize - 1) {
|
||||
var subarray = pending.subarray(0, pendingIndex);
|
||||
var arraylike = subarray;
|
||||
chunks.push(String.fromCharCode.apply(null, arraylike));
|
||||
if (!more) {
|
||||
return chunks.join("");
|
||||
}
|
||||
bytes = bytes.subarray(inputIndex);
|
||||
inputIndex = 0;
|
||||
pendingIndex = 0;
|
||||
}
|
||||
var byte1 = bytes[inputIndex++];
|
||||
if ((byte1 & 128) === 0) {
|
||||
pending[pendingIndex++] = byte1;
|
||||
} else if ((byte1 & 224) === 192) {
|
||||
var byte2 = bytes[inputIndex++] & 63;
|
||||
pending[pendingIndex++] = (byte1 & 31) << 6 | byte2;
|
||||
} else if ((byte1 & 240) === 224) {
|
||||
var byte2 = bytes[inputIndex++] & 63;
|
||||
var byte3 = bytes[inputIndex++] & 63;
|
||||
pending[pendingIndex++] = (byte1 & 31) << 12 | byte2 << 6 | byte3;
|
||||
} else if ((byte1 & 248) === 240) {
|
||||
var byte2 = bytes[inputIndex++] & 63;
|
||||
var byte3 = bytes[inputIndex++] & 63;
|
||||
var byte4 = bytes[inputIndex++] & 63;
|
||||
var codepoint = (byte1 & 7) << 18 | byte2 << 12 | byte3 << 6 | byte4;
|
||||
if (codepoint > 65535) {
|
||||
codepoint -= 65536;
|
||||
pending[pendingIndex++] = codepoint >>> 10 & 1023 | 55296;
|
||||
codepoint = 56320 | codepoint & 1023;
|
||||
}
|
||||
pending[pendingIndex++] = codepoint;
|
||||
} else ;
|
||||
}
|
||||
}
|
||||
function encodeFallback(string) {
|
||||
var pos = 0;
|
||||
var len = string.length;
|
||||
var at = 0;
|
||||
var tlen = Math.max(32, len + (len >>> 1) + 7);
|
||||
var target = new Uint8Array(tlen >>> 3 << 3);
|
||||
while (pos < len) {
|
||||
var value = string.charCodeAt(pos++);
|
||||
if (value >= 55296 && value <= 56319) {
|
||||
if (pos < len) {
|
||||
var extra = string.charCodeAt(pos);
|
||||
if ((extra & 64512) === 56320) {
|
||||
++pos;
|
||||
value = ((value & 1023) << 10) + (extra & 1023) + 65536;
|
||||
}
|
||||
}
|
||||
if (value >= 55296 && value <= 56319) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (at + 4 > target.length) {
|
||||
tlen += 8;
|
||||
tlen *= 1 + pos / string.length * 2;
|
||||
tlen = tlen >>> 3 << 3;
|
||||
var update = new Uint8Array(tlen);
|
||||
update.set(target);
|
||||
target = update;
|
||||
}
|
||||
if ((value & 4294967168) === 0) {
|
||||
target[at++] = value;
|
||||
continue;
|
||||
} else if ((value & 4294965248) === 0) {
|
||||
target[at++] = value >>> 6 & 31 | 192;
|
||||
} else if ((value & 4294901760) === 0) {
|
||||
target[at++] = value >>> 12 & 15 | 224;
|
||||
target[at++] = value >>> 6 & 63 | 128;
|
||||
} else if ((value & 4292870144) === 0) {
|
||||
target[at++] = value >>> 18 & 7 | 240;
|
||||
target[at++] = value >>> 12 & 63 | 128;
|
||||
target[at++] = value >>> 6 & 63 | 128;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
target[at++] = value & 63 | 128;
|
||||
}
|
||||
return target.slice ? target.slice(0, at) : target.subarray(0, at);
|
||||
}
|
||||
var StatusCodes = /* @__PURE__ */ ((StatusCodes2) => {
|
||||
StatusCodes2[StatusCodes2["CONTINUE"] = 100] = "CONTINUE";
|
||||
StatusCodes2[StatusCodes2["SWITCHING_PROTOCOLS"] = 101] = "SWITCHING_PROTOCOLS";
|
||||
StatusCodes2[StatusCodes2["PROCESSING"] = 102] = "PROCESSING";
|
||||
StatusCodes2[StatusCodes2["EARLY_HINTS"] = 103] = "EARLY_HINTS";
|
||||
StatusCodes2[StatusCodes2["OK"] = 200] = "OK";
|
||||
StatusCodes2[StatusCodes2["CREATED"] = 201] = "CREATED";
|
||||
StatusCodes2[StatusCodes2["ACCEPTED"] = 202] = "ACCEPTED";
|
||||
StatusCodes2[StatusCodes2["NON_AUTHORITATIVE_INFORMATION"] = 203] = "NON_AUTHORITATIVE_INFORMATION";
|
||||
StatusCodes2[StatusCodes2["NO_CONTENT"] = 204] = "NO_CONTENT";
|
||||
StatusCodes2[StatusCodes2["RESET_CONTENT"] = 205] = "RESET_CONTENT";
|
||||
StatusCodes2[StatusCodes2["PARTIAL_CONTENT"] = 206] = "PARTIAL_CONTENT";
|
||||
StatusCodes2[StatusCodes2["MULTI_STATUS"] = 207] = "MULTI_STATUS";
|
||||
StatusCodes2[StatusCodes2["MULTIPLE_CHOICES"] = 300] = "MULTIPLE_CHOICES";
|
||||
StatusCodes2[StatusCodes2["MOVED_PERMANENTLY"] = 301] = "MOVED_PERMANENTLY";
|
||||
StatusCodes2[StatusCodes2["MOVED_TEMPORARILY"] = 302] = "MOVED_TEMPORARILY";
|
||||
StatusCodes2[StatusCodes2["SEE_OTHER"] = 303] = "SEE_OTHER";
|
||||
StatusCodes2[StatusCodes2["NOT_MODIFIED"] = 304] = "NOT_MODIFIED";
|
||||
StatusCodes2[StatusCodes2["USE_PROXY"] = 305] = "USE_PROXY";
|
||||
StatusCodes2[StatusCodes2["TEMPORARY_REDIRECT"] = 307] = "TEMPORARY_REDIRECT";
|
||||
StatusCodes2[StatusCodes2["PERMANENT_REDIRECT"] = 308] = "PERMANENT_REDIRECT";
|
||||
StatusCodes2[StatusCodes2["BAD_REQUEST"] = 400] = "BAD_REQUEST";
|
||||
StatusCodes2[StatusCodes2["UNAUTHORIZED"] = 401] = "UNAUTHORIZED";
|
||||
StatusCodes2[StatusCodes2["PAYMENT_REQUIRED"] = 402] = "PAYMENT_REQUIRED";
|
||||
StatusCodes2[StatusCodes2["FORBIDDEN"] = 403] = "FORBIDDEN";
|
||||
StatusCodes2[StatusCodes2["NOT_FOUND"] = 404] = "NOT_FOUND";
|
||||
StatusCodes2[StatusCodes2["METHOD_NOT_ALLOWED"] = 405] = "METHOD_NOT_ALLOWED";
|
||||
StatusCodes2[StatusCodes2["NOT_ACCEPTABLE"] = 406] = "NOT_ACCEPTABLE";
|
||||
StatusCodes2[StatusCodes2["PROXY_AUTHENTICATION_REQUIRED"] = 407] = "PROXY_AUTHENTICATION_REQUIRED";
|
||||
StatusCodes2[StatusCodes2["REQUEST_TIMEOUT"] = 408] = "REQUEST_TIMEOUT";
|
||||
StatusCodes2[StatusCodes2["CONFLICT"] = 409] = "CONFLICT";
|
||||
StatusCodes2[StatusCodes2["GONE"] = 410] = "GONE";
|
||||
StatusCodes2[StatusCodes2["LENGTH_REQUIRED"] = 411] = "LENGTH_REQUIRED";
|
||||
StatusCodes2[StatusCodes2["PRECONDITION_FAILED"] = 412] = "PRECONDITION_FAILED";
|
||||
StatusCodes2[StatusCodes2["REQUEST_TOO_LONG"] = 413] = "REQUEST_TOO_LONG";
|
||||
StatusCodes2[StatusCodes2["REQUEST_URI_TOO_LONG"] = 414] = "REQUEST_URI_TOO_LONG";
|
||||
StatusCodes2[StatusCodes2["UNSUPPORTED_MEDIA_TYPE"] = 415] = "UNSUPPORTED_MEDIA_TYPE";
|
||||
StatusCodes2[StatusCodes2["REQUESTED_RANGE_NOT_SATISFIABLE"] = 416] = "REQUESTED_RANGE_NOT_SATISFIABLE";
|
||||
StatusCodes2[StatusCodes2["EXPECTATION_FAILED"] = 417] = "EXPECTATION_FAILED";
|
||||
StatusCodes2[StatusCodes2["IM_A_TEAPOT"] = 418] = "IM_A_TEAPOT";
|
||||
StatusCodes2[StatusCodes2["INSUFFICIENT_SPACE_ON_RESOURCE"] = 419] = "INSUFFICIENT_SPACE_ON_RESOURCE";
|
||||
StatusCodes2[StatusCodes2["METHOD_FAILURE"] = 420] = "METHOD_FAILURE";
|
||||
StatusCodes2[StatusCodes2["MISDIRECTED_REQUEST"] = 421] = "MISDIRECTED_REQUEST";
|
||||
StatusCodes2[StatusCodes2["UNPROCESSABLE_ENTITY"] = 422] = "UNPROCESSABLE_ENTITY";
|
||||
StatusCodes2[StatusCodes2["LOCKED"] = 423] = "LOCKED";
|
||||
StatusCodes2[StatusCodes2["FAILED_DEPENDENCY"] = 424] = "FAILED_DEPENDENCY";
|
||||
StatusCodes2[StatusCodes2["UPGRADE_REQUIRED"] = 426] = "UPGRADE_REQUIRED";
|
||||
StatusCodes2[StatusCodes2["PRECONDITION_REQUIRED"] = 428] = "PRECONDITION_REQUIRED";
|
||||
StatusCodes2[StatusCodes2["TOO_MANY_REQUESTS"] = 429] = "TOO_MANY_REQUESTS";
|
||||
StatusCodes2[StatusCodes2["REQUEST_HEADER_FIELDS_TOO_LARGE"] = 431] = "REQUEST_HEADER_FIELDS_TOO_LARGE";
|
||||
StatusCodes2[StatusCodes2["UNAVAILABLE_FOR_LEGAL_REASONS"] = 451] = "UNAVAILABLE_FOR_LEGAL_REASONS";
|
||||
StatusCodes2[StatusCodes2["INTERNAL_SERVER_ERROR"] = 500] = "INTERNAL_SERVER_ERROR";
|
||||
StatusCodes2[StatusCodes2["NOT_IMPLEMENTED"] = 501] = "NOT_IMPLEMENTED";
|
||||
StatusCodes2[StatusCodes2["BAD_GATEWAY"] = 502] = "BAD_GATEWAY";
|
||||
StatusCodes2[StatusCodes2["SERVICE_UNAVAILABLE"] = 503] = "SERVICE_UNAVAILABLE";
|
||||
StatusCodes2[StatusCodes2["GATEWAY_TIMEOUT"] = 504] = "GATEWAY_TIMEOUT";
|
||||
StatusCodes2[StatusCodes2["HTTP_VERSION_NOT_SUPPORTED"] = 505] = "HTTP_VERSION_NOT_SUPPORTED";
|
||||
StatusCodes2[StatusCodes2["INSUFFICIENT_STORAGE"] = 507] = "INSUFFICIENT_STORAGE";
|
||||
StatusCodes2[StatusCodes2["NETWORK_AUTHENTICATION_REQUIRED"] = 511] = "NETWORK_AUTHENTICATION_REQUIRED";
|
||||
return StatusCodes2;
|
||||
})(StatusCodes || {});
|
||||
class HttpError extends Error {
|
||||
statusCode;
|
||||
headers;
|
||||
constructor(statusCode, message, headers) {
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
this.headers = headers;
|
||||
}
|
||||
toString() {
|
||||
return `HttpError(${this.statusCode}, ${this.message})`;
|
||||
}
|
||||
toResponse() {
|
||||
const m = this.message;
|
||||
return {
|
||||
headers: this.headers,
|
||||
status: this.statusCode,
|
||||
body: m !== "" ? encodeFallback(m) : void 0
|
||||
};
|
||||
}
|
||||
}
|
||||
function stringHandler(f) {
|
||||
return async (req) => {
|
||||
try {
|
||||
const body = req.body;
|
||||
const resp = await f({
|
||||
uri: req.uri,
|
||||
params: req.params,
|
||||
headers: req.headers,
|
||||
user: req.user,
|
||||
body: body && decodeFallback(body)
|
||||
});
|
||||
if (resp === void 0) {
|
||||
return void 0;
|
||||
}
|
||||
if (typeof resp === "string") {
|
||||
return {
|
||||
status: 200,
|
||||
body: encodeFallback(resp)
|
||||
};
|
||||
}
|
||||
const respBody = resp.body;
|
||||
return {
|
||||
headers: resp.headers,
|
||||
status: resp.status,
|
||||
body: respBody ? encodeFallback(respBody) : void 0
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof HttpError) {
|
||||
return err.toResponse();
|
||||
}
|
||||
return {
|
||||
status: 500,
|
||||
body: encodeFallback(`Uncaught error: ${err}`)
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
function htmlHandler(f) {
|
||||
return async (req) => {
|
||||
try {
|
||||
const body = req.body;
|
||||
const resp = await f({
|
||||
uri: req.uri,
|
||||
params: req.params,
|
||||
headers: req.headers,
|
||||
user: req.user,
|
||||
body: body && decodeFallback(body)
|
||||
});
|
||||
if (resp === void 0) {
|
||||
return void 0;
|
||||
}
|
||||
if (typeof resp === "string") {
|
||||
return {
|
||||
headers: [["content-type", "text/html"]],
|
||||
status: 200,
|
||||
body: encodeFallback(resp)
|
||||
};
|
||||
}
|
||||
const respBody = resp.body;
|
||||
return {
|
||||
headers: [["content-type", "text/html"], ...resp.headers ?? []],
|
||||
status: resp.status,
|
||||
body: respBody ? encodeFallback(respBody) : void 0
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof HttpError) {
|
||||
return err.toResponse();
|
||||
}
|
||||
return {
|
||||
status: 500,
|
||||
body: encodeFallback(`Uncaught error: ${err}`)
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
function jsonHandler(f) {
|
||||
return async (req) => {
|
||||
try {
|
||||
const body = req.body;
|
||||
const resp = await f({
|
||||
uri: req.uri,
|
||||
params: req.params,
|
||||
headers: req.headers,
|
||||
user: req.user,
|
||||
body: body && decodeFallback(body)
|
||||
});
|
||||
if (resp === void 0) {
|
||||
return void 0;
|
||||
}
|
||||
if ("body" in resp) {
|
||||
const r = resp;
|
||||
const rBody = r.body;
|
||||
return {
|
||||
headers: [["content-type", "application/json"], ...r.headers ?? []],
|
||||
status: r.status,
|
||||
body: rBody ? encodeFallback(JSON.stringify(rBody)) : void 0
|
||||
};
|
||||
}
|
||||
return {
|
||||
headers: [["content-type", "application/json"]],
|
||||
status: 200,
|
||||
body: encodeFallback(JSON.stringify(resp))
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof HttpError) {
|
||||
return err.toResponse();
|
||||
}
|
||||
return {
|
||||
headers: [["content-type", "application/json"]],
|
||||
status: 500,
|
||||
body: encodeFallback(`Uncaught error: ${err}`)
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
const routerCallbacks = /* @__PURE__ */ new Map();
|
||||
function isolateId() {
|
||||
return rustyscript.functions.isolate_id();
|
||||
}
|
||||
function addRoute(method, route, callback) {
|
||||
if (isolateId() === 0) {
|
||||
rustyscript.functions.install_route(method, route);
|
||||
console.debug("JS: Added route:", method, route);
|
||||
}
|
||||
routerCallbacks.set(`${method}:${route}`, callback);
|
||||
}
|
||||
async function dispatch(method, route, uri, pathParams, headers, user, body) {
|
||||
const key = `${method}:${route}`;
|
||||
const cb = routerCallbacks.get(key);
|
||||
if (!cb) {
|
||||
throw Error(`Missing callback: ${key}`);
|
||||
}
|
||||
return await cb({
|
||||
uri,
|
||||
params: Object.fromEntries(pathParams),
|
||||
headers: Object.fromEntries(headers),
|
||||
user,
|
||||
body
|
||||
}) ?? {
|
||||
status: 200
|
||||
/* OK */
|
||||
};
|
||||
}
|
||||
globalThis.__dispatch = dispatch;
|
||||
const cronCallbacks = /* @__PURE__ */ new Map();
|
||||
function addCronCallback(name, schedule, cb) {
|
||||
const cronRegex = /^(@(yearly|monthly|weekly|daily|hourly|))|((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*)\s*){6,7})$/;
|
||||
const matches = cronRegex.test(schedule);
|
||||
if (!matches) {
|
||||
throw Error(`Not a valid 6/7-component cron schedule: ${schedule}`);
|
||||
}
|
||||
if (isolateId() === 0) {
|
||||
const id = rustyscript.functions.install_job(name, schedule);
|
||||
console.debug(`JS: Added cron job (id=${id}): "${name}"`);
|
||||
cronCallbacks.set(id, cb);
|
||||
}
|
||||
}
|
||||
async function dispatchCron(id) {
|
||||
const cb = cronCallbacks.get(id);
|
||||
if (!cb) {
|
||||
throw Error(`Missing cron callback: ${id}`);
|
||||
}
|
||||
try {
|
||||
await cb();
|
||||
} catch (err) {
|
||||
return `${err}`;
|
||||
}
|
||||
}
|
||||
globalThis.__dispatchCron = dispatchCron;
|
||||
function addPeriodicCallback(milliseconds, cb) {
|
||||
if (isolateId() !== 0) {
|
||||
return () => {
|
||||
};
|
||||
}
|
||||
const handle = setInterval(() => {
|
||||
cb(() => clearInterval(handle));
|
||||
}, milliseconds);
|
||||
return () => clearInterval(handle);
|
||||
}
|
||||
async function query(sql, params) {
|
||||
return await rustyscript.async_functions.query(sql, params);
|
||||
}
|
||||
async function execute(sql, params) {
|
||||
return await rustyscript.async_functions.execute(sql, params);
|
||||
}
|
||||
class Transaction {
|
||||
finalized;
|
||||
constructor() {
|
||||
this.finalized = false;
|
||||
}
|
||||
query(queryStr, params) {
|
||||
return rustyscript.functions.transaction_query(queryStr, params);
|
||||
}
|
||||
execute(queryStr, params) {
|
||||
return rustyscript.functions.transaction_execute(queryStr, params);
|
||||
}
|
||||
commit() {
|
||||
this.finalized = true;
|
||||
rustyscript.functions.transaction_commit();
|
||||
}
|
||||
rollback() {
|
||||
this.finalized = true;
|
||||
rustyscript.functions.transaction_rollback();
|
||||
}
|
||||
}
|
||||
async function transaction(f) {
|
||||
await rustyscript.async_functions.transaction_begin();
|
||||
const tx = new Transaction();
|
||||
try {
|
||||
const r = f(tx);
|
||||
if (!tx.finalized) {
|
||||
rustyscript.functions.transaction_rollback();
|
||||
}
|
||||
return r;
|
||||
} catch (e) {
|
||||
rustyscript.functions.transaction_rollback();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
function parsePath(path) {
|
||||
const queryIndex = path.indexOf("?");
|
||||
if (queryIndex >= 0) {
|
||||
return {
|
||||
path: path.slice(0, queryIndex),
|
||||
query: new URLSearchParams(path.slice(queryIndex + 1))
|
||||
};
|
||||
}
|
||||
return {
|
||||
path,
|
||||
query: new URLSearchParams()
|
||||
};
|
||||
}
|
||||
const _logStderr = function(...args) {
|
||||
globalThis.Deno.core.print(
|
||||
`${args.join(" ")}
|
||||
`,
|
||||
/* to stderr = */
|
||||
true
|
||||
);
|
||||
};
|
||||
globalThis.console.log = _logStderr;
|
||||
globalThis.console.info = _logStderr;
|
||||
globalThis.console.debug = _logStderr;
|
||||
export {
|
||||
HttpError,
|
||||
StatusCodes,
|
||||
Transaction,
|
||||
addCronCallback,
|
||||
addPeriodicCallback,
|
||||
addRoute,
|
||||
execute,
|
||||
fs,
|
||||
htmlHandler,
|
||||
jsonHandler,
|
||||
parsePath,
|
||||
query,
|
||||
stringHandler,
|
||||
transaction
|
||||
};
|
||||
@@ -28,8 +28,5 @@
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"node_modules",
|
||||
],
|
||||
"exclude": ["dist", "node_modules"]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
plugins: [react(), tailwindcss()],
|
||||
});
|
||||
|
||||
Generated
+13
@@ -368,6 +368,9 @@ importers:
|
||||
react-dom:
|
||||
specifier: ^19.1.0
|
||||
version: 19.1.0(react@19.1.0)
|
||||
tailwindcss:
|
||||
specifier: ^4.1.11
|
||||
version: 4.1.11
|
||||
trailbase:
|
||||
specifier: workspace:*
|
||||
version: link:../../trailbase-assets/js/client
|
||||
@@ -375,6 +378,9 @@ importers:
|
||||
'@eslint/js':
|
||||
specifier: ^9.31.0
|
||||
version: 9.31.0
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.1.11
|
||||
version: 4.1.11(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(yaml@2.8.0))
|
||||
'@types/react':
|
||||
specifier: ^19.1.8
|
||||
version: 19.1.8
|
||||
@@ -7945,6 +7951,13 @@ snapshots:
|
||||
tailwindcss: 4.1.11
|
||||
vite: 6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(yaml@2.8.0)
|
||||
|
||||
'@tailwindcss/vite@4.1.11(vite@7.0.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@tailwindcss/node': 4.1.11
|
||||
'@tailwindcss/oxide': 4.1.11
|
||||
tailwindcss: 4.1.11
|
||||
vite: 7.0.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(yaml@2.8.0)
|
||||
|
||||
'@tanstack/db@0.0.27(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@electric-sql/d2mini': 0.1.7
|
||||
|
||||
Reference in New Issue
Block a user