mirror of
https://github.com/trailbaseio/trailbase.git
synced 2025-12-17 23:54:28 -06:00
Add a new vector search + Web UI example.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,4 +10,3 @@ node_modules/
|
||||
|
||||
# Dev artifacts
|
||||
public/
|
||||
traildepot/
|
||||
|
||||
3
examples/blog/traildepot/.gitignore
vendored
3
examples/blog/traildepot/.gitignore
vendored
@@ -5,3 +5,6 @@ secrets/
|
||||
uploads/
|
||||
|
||||
!migrations/
|
||||
|
||||
trailbase.js
|
||||
trailbase.d.ts
|
||||
|
||||
24
examples/coffeesearch/.gitignore
vendored
Normal file
24
examples/coffeesearch/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
19
examples/coffeesearch/Dockerfile
Normal file
19
examples/coffeesearch/Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
||||
FROM trailbase/trailbase:0.2.1 AS base
|
||||
|
||||
COPY traildepot /app/traildepot
|
||||
COPY dist /app/public
|
||||
|
||||
USER root
|
||||
|
||||
RUN mkdir -p /app/traildepot/data
|
||||
RUN chown -R trailbase /app/traildepot/data
|
||||
RUN chmod +w /app/traildepot/data
|
||||
|
||||
|
||||
USER trailbase
|
||||
|
||||
EXPOSE 4000
|
||||
ENTRYPOINT ["tini", "--"]
|
||||
CMD ["/app/trail", "--data-dir", "/app/traildepot", "run", "--address", "0.0.0.0:4000", "--public-dir", "/app/public"]
|
||||
|
||||
HEALTHCHECK CMD curl --fail http://localhost:4000/api/healthcheck || exit 1
|
||||
9
examples/coffeesearch/Makefile
Normal file
9
examples/coffeesearch/Makefile
Normal file
@@ -0,0 +1,9 @@
|
||||
all: init app
|
||||
|
||||
app: dist
|
||||
pnpm build
|
||||
|
||||
init:
|
||||
mkdir -p traildepot/data; cat import.sql | sqlite3 traildepot/data/main.db -
|
||||
|
||||
.PHONY: init
|
||||
15
examples/coffeesearch/README.md
Normal file
15
examples/coffeesearch/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Coffee Search
|
||||
|
||||
A small web application demonstrating the use of TrailBase and its vector
|
||||
search to build a coffee search.
|
||||
|
||||
To import the coffee data from CSV, run:
|
||||
|
||||
```bash
|
||||
mkdir -p traildepot/data
|
||||
cat import.sql | sqlite3 traildepot/data/main.db -
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
* Coffee data [source](https://github.com/jldbc/coffee-quality-database/blob/master/data/arabica_data_cleaned.csv)
|
||||
1318
examples/coffeesearch/arabica_data_cleaned.csv
Normal file
1318
examples/coffeesearch/arabica_data_cleaned.csv
Normal file
File diff suppressed because it is too large
Load Diff
28
examples/coffeesearch/eslint.config.js
Normal file
28
examples/coffeesearch/eslint.config.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ['dist'] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
||||
35
examples/coffeesearch/import.sql
Normal file
35
examples/coffeesearch/import.sql
Normal file
@@ -0,0 +1,35 @@
|
||||
-- Create table if it doesn't exist.
|
||||
CREATE TABLE IF NOT EXISTS coffee (
|
||||
Species TEXT,
|
||||
Owner TEXT,
|
||||
|
||||
Aroma REAL,
|
||||
Flavor REAL,
|
||||
Acidity REAL,
|
||||
Sweetness REAL,
|
||||
|
||||
embedding BLOB
|
||||
) STRICT;
|
||||
|
||||
-- Empty table for clean import.
|
||||
DELETE FROM coffee;
|
||||
|
||||
-- Go on to import data.
|
||||
DROP TABLE IF EXISTS temporary;
|
||||
|
||||
.mode csv
|
||||
.import arabica_data_cleaned.csv temporary
|
||||
|
||||
INSERT INTO coffee (Species, Owner, Aroma, Flavor, Acidity, Sweetness)
|
||||
SELECT
|
||||
Species,
|
||||
Owner,
|
||||
|
||||
CAST(Aroma AS REAL) AS Aroma,
|
||||
CAST(Flavor AS REAL) AS Flavor,
|
||||
CAST(Acidity AS REAL) AS Acidity,
|
||||
CAST(Sweetness AS REAL) AS Sweetness
|
||||
FROM temporary;
|
||||
|
||||
-- Clean up.
|
||||
DROP TABLE temporary;
|
||||
14
examples/coffeesearch/index.html
Normal file
14
examples/coffeesearch/index.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<title>Coffee Search</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
31
examples/coffeesearch/package.json
Normal file
31
examples/coffeesearch/package.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "my-project",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"format": "prettier -w src traildepot/scripts",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.13.0",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react": "^4.3.3",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.14",
|
||||
"globals": "^15.11.0",
|
||||
"prettier": "^3.3.3",
|
||||
"typescript": "~5.6.2",
|
||||
"typescript-eslint": "^8.11.0",
|
||||
"vite": "^5.4.10"
|
||||
}
|
||||
}
|
||||
42
examples/coffeesearch/src/App.css
Normal file
42
examples/coffeesearch/src/App.css
Normal file
@@ -0,0 +1,42 @@
|
||||
#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;
|
||||
}
|
||||
97
examples/coffeesearch/src/App.tsx
Normal file
97
examples/coffeesearch/src/App.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import "./App.css";
|
||||
|
||||
type Data = Array<Array<object>>;
|
||||
|
||||
async function getData(v: {
|
||||
aroma: number;
|
||||
flavor: number;
|
||||
acidity: number;
|
||||
sweetness: number;
|
||||
}): Promise<Data> {
|
||||
const URL = import.meta.env.DEV ? "http://localhost:4000" : "";
|
||||
const params = Object.entries(v)
|
||||
.map(([k, v]) => `${k}=${v}`)
|
||||
.join("&");
|
||||
const response = await fetch(`${URL}/search?${params}`);
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
function Input(props: {
|
||||
label: string;
|
||||
value: number;
|
||||
update: (v: number) => void;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<label>{props.label}:</label>
|
||||
<input
|
||||
type="number"
|
||||
step="0.1"
|
||||
value={props.value}
|
||||
onChange={(el) => props.update(el.target.valueAsNumber)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Table() {
|
||||
const [aroma, setAroma] = useState(8);
|
||||
const [flavor, setFlavor] = useState(8);
|
||||
const [acidity, setAcidity] = useState(8);
|
||||
const [sweetness, setSweetness] = useState(8);
|
||||
|
||||
const [data, setData] = useState<Data | undefined>();
|
||||
useEffect(() => {
|
||||
setData(undefined);
|
||||
getData({ aroma, flavor, acidity, sweetness }).then((data) =>
|
||||
setData(data),
|
||||
);
|
||||
}, [aroma, flavor, acidity, sweetness]);
|
||||
|
||||
const Row = (props: { row: Array<object> }) => (
|
||||
<tr>
|
||||
{props.row.map((d) => (
|
||||
<td>{`${d}`}</td>
|
||||
))}
|
||||
</tr>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="inputs">
|
||||
<Input label="Aroma" value={aroma} update={setAroma} />
|
||||
<Input label="Flavor" value={flavor} update={setFlavor} />
|
||||
<Input label="Acidity" value={acidity} update={setAcidity} />
|
||||
<Input label="Sweetness" value={sweetness} update={setSweetness} />
|
||||
</div>
|
||||
|
||||
<div className="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Owner</th>
|
||||
<th scope="col">Aroma</th>
|
||||
<th scope="col">Flavor</th>
|
||||
<th scope="col">Acidity</th>
|
||||
<th scope="col">Sweetness</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{(data ?? []).map((row) => (
|
||||
<Row row={row} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const App = () => (
|
||||
<>
|
||||
<h1>Coffee Search</h1>
|
||||
<Table />
|
||||
</>
|
||||
);
|
||||
31
examples/coffeesearch/src/index.css
Normal file
31
examples/coffeesearch/src/index.css
Normal file
@@ -0,0 +1,31 @@
|
||||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
color: #213547;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.inputs {
|
||||
margin: 1em;
|
||||
display: grid;
|
||||
gap: 1rem 0px;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
10
examples/coffeesearch/src/main.tsx
Normal file
10
examples/coffeesearch/src/main.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import "./index.css";
|
||||
import { App } from "./App.tsx";
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
);
|
||||
1
examples/coffeesearch/src/vite-env.d.ts
vendored
Normal file
1
examples/coffeesearch/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
5
examples/coffeesearch/traildepot/.gitignore
vendored
Normal file
5
examples/coffeesearch/traildepot/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
backups/
|
||||
data/
|
||||
secrets/
|
||||
uploads/
|
||||
35
examples/coffeesearch/traildepot/config.textproto
Normal file
35
examples/coffeesearch/traildepot/config.textproto
Normal file
@@ -0,0 +1,35 @@
|
||||
# Auto-generated config.Config textproto
|
||||
email {
|
||||
user_verification_template {
|
||||
subject: "Validate 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>"
|
||||
}
|
||||
}
|
||||
server {
|
||||
application_name: "TrailBase"
|
||||
site_url: "http://localhost:4000"
|
||||
logs_retention_sec: 604800
|
||||
}
|
||||
auth {
|
||||
auth_token_ttl_sec: 120
|
||||
refresh_token_ttl_sec: 2592000
|
||||
}
|
||||
record_apis: [{
|
||||
name: "_user_avatar"
|
||||
table_name: "_user_avatar"
|
||||
conflict_resolution: REPLACE
|
||||
autofill_missing_user_id_columns: true
|
||||
acl_world: [READ]
|
||||
acl_authenticated: [CREATE, READ, UPDATE, DELETE]
|
||||
create_access_rule: "_REQ_.user IS NULL OR _REQ_.user = _USER_.id"
|
||||
update_access_rule: "_ROW_.user = _USER_.id"
|
||||
delete_access_rule: "_ROW_.user = _USER_.id"
|
||||
}]
|
||||
@@ -0,0 +1,13 @@
|
||||
CREATE TABLE IF NOT EXISTS coffee (
|
||||
Species TEXT NOT NULL,
|
||||
Owner TEXT NOT NULL,
|
||||
|
||||
Aroma REAL NOT NULL,
|
||||
Flavor REAL NOT NULL,
|
||||
Acidity REAL NOT NULL,
|
||||
Sweetness REAL NOT NULL,
|
||||
|
||||
embedding BLOB
|
||||
) STRICT;
|
||||
|
||||
UPDATE coffee SET embedding = VECTOR(FORMAT("[%f, %f, %f, %f]", Aroma, Flavor, Acidity, Sweetness));
|
||||
30
examples/coffeesearch/traildepot/scripts/index.ts
Normal file
30
examples/coffeesearch/traildepot/scripts/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { addRoute, jsonHandler, parsePath, query } from "../trailbase.js";
|
||||
import type { JsonRequestType } from "../trailbase.js";
|
||||
|
||||
addRoute(
|
||||
"GET",
|
||||
"/search",
|
||||
jsonHandler(async (req: JsonRequestType) => {
|
||||
const searchParams = parsePath(req.uri).query;
|
||||
|
||||
const aroma = searchParams?.get("aroma") ?? 8;
|
||||
const flavor = searchParams?.get("flavor") ?? 8;
|
||||
const acidity = searchParams?.get("acidity") ?? 8;
|
||||
const sweetness = searchParams?.get("sweetness") ?? 8;
|
||||
|
||||
return await query(
|
||||
`
|
||||
SELECT
|
||||
Owner, Aroma, Flavor, Acidity, Sweetness
|
||||
FROM
|
||||
coffee
|
||||
WHERE
|
||||
Aroma IS NOT NULL
|
||||
ORDER BY
|
||||
vector_distance_cos(embedding, '[${aroma}, ${flavor}, ${acidity}, ${sweetness}]')
|
||||
LIMIT 100
|
||||
`,
|
||||
[],
|
||||
);
|
||||
}),
|
||||
);
|
||||
135
examples/coffeesearch/traildepot/trailbase.d.ts
vendored
Normal file
135
examples/coffeesearch/traildepot/trailbase.d.ts
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
export type HeaderMapType = {
|
||||
[key: string]: string;
|
||||
};
|
||||
export type PathParamsType = {
|
||||
[key: string]: string;
|
||||
};
|
||||
export type UserType = {
|
||||
id: string;
|
||||
email: string;
|
||||
csrf: string;
|
||||
};
|
||||
export type RequestType = {
|
||||
uri: string;
|
||||
params: PathParamsType;
|
||||
headers: HeaderMapType;
|
||||
user?: UserType;
|
||||
body?: Uint8Array;
|
||||
};
|
||||
export type ResponseType = {
|
||||
headers?: [string, string][];
|
||||
status?: number;
|
||||
body?: Uint8Array;
|
||||
};
|
||||
export type MaybeResponse<T> = Promise<T | undefined> | T | undefined;
|
||||
export type CallbackType = (req: RequestType) => MaybeResponse<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 class HttpError extends Error {
|
||||
readonly statusCode: number;
|
||||
readonly headers: [string, string][] | undefined;
|
||||
constructor(statusCode: number, message?: string, headers?: [string, string][]);
|
||||
toString(): string;
|
||||
toResponse(): ResponseType;
|
||||
}
|
||||
export type StringRequestType = {
|
||||
uri: string;
|
||||
params: PathParamsType;
|
||||
headers: HeaderMapType;
|
||||
user?: UserType;
|
||||
body?: string;
|
||||
};
|
||||
export type StringResponseType = {
|
||||
headers?: [string, string][];
|
||||
status?: number;
|
||||
body: string;
|
||||
};
|
||||
export declare function stringHandler(f: (req: StringRequestType) => MaybeResponse<StringResponseType | string>): CallbackType;
|
||||
export type HtmlResponseType = {
|
||||
headers?: [string, string][];
|
||||
status?: number;
|
||||
body: string;
|
||||
};
|
||||
export declare function htmlHandler(f: (req: StringRequestType) => MaybeResponse<HtmlResponseType | string>): CallbackType;
|
||||
export type JsonRequestType = {
|
||||
uri: string;
|
||||
params: PathParamsType;
|
||||
headers: HeaderMapType;
|
||||
user?: UserType;
|
||||
body?: object | string;
|
||||
};
|
||||
export interface JsonResponseType {
|
||||
headers?: [string, string][];
|
||||
status?: number;
|
||||
body: object;
|
||||
}
|
||||
export declare function jsonHandler(f: (req: JsonRequestType) => MaybeResponse<JsonRequestType | object>): CallbackType;
|
||||
export declare function addRoute(method: string, route: string, callback: CallbackType): void;
|
||||
export declare function dispatch(method: string, route: string, uri: string, pathParams: [string, string][], headers: [string, string][], user: UserType | undefined, body: Uint8Array): Promise<ResponseType>;
|
||||
export declare function query(queryStr: string, params: unknown[]): Promise<unknown[][]>;
|
||||
export declare function execute(queryStr: string, params: unknown[]): Promise<number>;
|
||||
export type ParsedPath = {
|
||||
path: string;
|
||||
query: URLSearchParams;
|
||||
};
|
||||
export declare function parsePath(path: string): ParsedPath;
|
||||
export declare function decodeFallback(bytes: Uint8Array): string;
|
||||
export declare function encodeFallback(string: string): Uint8Array;
|
||||
665
examples/coffeesearch/traildepot/trailbase.js
Normal file
665
examples/coffeesearch/traildepot/trailbase.js
Normal file
@@ -0,0 +1,665 @@
|
||||
/// HTTP status codes.
|
||||
///
|
||||
// source: https://github.com/prettymuchbryce/http-status-codes/blob/master/src/status-codes.ts
|
||||
export var StatusCodes;
|
||||
(function (StatusCodes) {
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.2.1
|
||||
///
|
||||
/// This interim response indicates that everything so far is OK and that the
|
||||
/// client should continue with the request or ignore it if it is already
|
||||
/// finished.
|
||||
StatusCodes[StatusCodes["CONTINUE"] = 100] = "CONTINUE";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.2.2
|
||||
///
|
||||
/// This code is sent in response to an Upgrade request header by the client,
|
||||
/// and indicates the protocol the server is switching too.
|
||||
StatusCodes[StatusCodes["SWITCHING_PROTOCOLS"] = 101] = "SWITCHING_PROTOCOLS";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.1
|
||||
///
|
||||
/// This code indicates that the server has received and is processing the
|
||||
/// request, but no response is available yet.
|
||||
StatusCodes[StatusCodes["PROCESSING"] = 102] = "PROCESSING";
|
||||
/// Official Documentation @ https://www.rfc-editor.org/rfc/rfc8297#page-3
|
||||
///
|
||||
/// This code indicates to the client that the server is likely to send a
|
||||
/// final response with the header fields included in the informational
|
||||
/// response.
|
||||
StatusCodes[StatusCodes["EARLY_HINTS"] = 103] = "EARLY_HINTS";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.1
|
||||
///
|
||||
/// The request has succeeded. The meaning of a success varies depending on the HTTP method:
|
||||
/// GET: The resource has been fetched and is transmitted in the message body.
|
||||
/// HEAD: The entity headers are in the message body.
|
||||
/// POST: The resource describing the result of the action is transmitted in the message body.
|
||||
/// TRACE: The message body contains the request message as received by the server
|
||||
StatusCodes[StatusCodes["OK"] = 200] = "OK";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.2
|
||||
///
|
||||
/// The request has succeeded and a new resource has been created as a result
|
||||
/// of it. This is typically the response sent after a PUT request.
|
||||
StatusCodes[StatusCodes["CREATED"] = 201] = "CREATED";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.3
|
||||
///
|
||||
/// The request has been received but not yet acted upon. It is
|
||||
/// non-committal, meaning that there is no way in HTTP to later send an
|
||||
/// asynchronous response indicating the outcome of processing the request. It
|
||||
/// is intended for cases where another process or server handles the request,
|
||||
/// or for batch processing.
|
||||
StatusCodes[StatusCodes["ACCEPTED"] = 202] = "ACCEPTED";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.4
|
||||
///
|
||||
/// This response code means returned meta-information set is not exact set
|
||||
/// as available from the origin server, but collected from a local or a third
|
||||
/// party copy. Except this condition, 200 OK response should be preferred
|
||||
/// instead of this response.
|
||||
StatusCodes[StatusCodes["NON_AUTHORITATIVE_INFORMATION"] = 203] = "NON_AUTHORITATIVE_INFORMATION";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.5
|
||||
///
|
||||
/// There is no content to send for this request, but the headers may be
|
||||
/// useful. The user-agent may update its cached headers for this resource with
|
||||
/// the new ones.
|
||||
StatusCodes[StatusCodes["NO_CONTENT"] = 204] = "NO_CONTENT";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.6
|
||||
///
|
||||
/// This response code is sent after accomplishing request to tell user agent
|
||||
/// reset document view which sent this request.
|
||||
StatusCodes[StatusCodes["RESET_CONTENT"] = 205] = "RESET_CONTENT";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7233#section-4.1
|
||||
///
|
||||
/// This response code is used because of range header sent by the client to
|
||||
/// separate download into multiple streams.
|
||||
StatusCodes[StatusCodes["PARTIAL_CONTENT"] = 206] = "PARTIAL_CONTENT";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.2
|
||||
///
|
||||
/// A Multi-Status response conveys information about multiple resources in
|
||||
/// situations where multiple status codes might be appropriate.
|
||||
StatusCodes[StatusCodes["MULTI_STATUS"] = 207] = "MULTI_STATUS";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.1
|
||||
///
|
||||
/// The request has more than one possible responses. User-agent or user
|
||||
/// should choose one of them. There is no standardized way to choose one of
|
||||
/// the responses.
|
||||
StatusCodes[StatusCodes["MULTIPLE_CHOICES"] = 300] = "MULTIPLE_CHOICES";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.2
|
||||
///
|
||||
/// This response code means that URI of requested resource has been changed.
|
||||
/// Probably, new URI would be given in the response.
|
||||
StatusCodes[StatusCodes["MOVED_PERMANENTLY"] = 301] = "MOVED_PERMANENTLY";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.3
|
||||
///
|
||||
/// This response code means that URI of requested resource has been changed
|
||||
/// temporarily. New changes in the URI might be made in the future. Therefore,
|
||||
/// this same URI should be used by the client in future requests.
|
||||
StatusCodes[StatusCodes["MOVED_TEMPORARILY"] = 302] = "MOVED_TEMPORARILY";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.4
|
||||
///
|
||||
/// Server sent this response to directing client to get requested resource
|
||||
/// to another URI with an GET request.
|
||||
StatusCodes[StatusCodes["SEE_OTHER"] = 303] = "SEE_OTHER";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7232#section-4.1
|
||||
///
|
||||
/// This is used for caching purposes. It is telling to client that response
|
||||
/// has not been modified. So, client can continue to use same cached version
|
||||
/// of response.
|
||||
StatusCodes[StatusCodes["NOT_MODIFIED"] = 304] = "NOT_MODIFIED";
|
||||
/// @deprecated
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.6
|
||||
///
|
||||
/// Was defined in a previous version of the HTTP specification to indicate
|
||||
/// that a requested response must be accessed by a proxy. It has been
|
||||
/// deprecated due to security concerns regarding in-band configuration of a
|
||||
/// proxy.
|
||||
StatusCodes[StatusCodes["USE_PROXY"] = 305] = "USE_PROXY";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.7
|
||||
///
|
||||
/// Server sent this response to directing client to get requested resource
|
||||
/// to another URI with same method that used prior request. This has the same
|
||||
/// semantic than the 302 Found HTTP response code, with the exception that the
|
||||
/// user agent must not change the HTTP method used: if a POST was used in the
|
||||
/// first request, a POST must be used in the second request.
|
||||
StatusCodes[StatusCodes["TEMPORARY_REDIRECT"] = 307] = "TEMPORARY_REDIRECT";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7538#section-3
|
||||
///
|
||||
/// This means that the resource is now permanently located at another URI,
|
||||
/// specified by the Location: HTTP Response header. This has the same
|
||||
/// semantics as the 301 Moved Permanently HTTP response code, with the
|
||||
/// exception that the user agent must not change the HTTP method used: if a
|
||||
/// POST was used in the first request, a POST must be used in the second
|
||||
/// request.
|
||||
StatusCodes[StatusCodes["PERMANENT_REDIRECT"] = 308] = "PERMANENT_REDIRECT";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.1
|
||||
///
|
||||
/// This response means that server could not understand the request due to invalid syntax.
|
||||
StatusCodes[StatusCodes["BAD_REQUEST"] = 400] = "BAD_REQUEST";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7235#section-3.1
|
||||
///
|
||||
/// Although the HTTP standard specifies "unauthorized", semantically this
|
||||
/// response means "unauthenticated". That is, the client must authenticate
|
||||
/// itself to get the requested response.
|
||||
StatusCodes[StatusCodes["UNAUTHORIZED"] = 401] = "UNAUTHORIZED";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.2
|
||||
///
|
||||
/// This response code is reserved for future use. Initial aim for creating
|
||||
/// this code was using it for digital payment systems however this is not used
|
||||
/// currently.
|
||||
StatusCodes[StatusCodes["PAYMENT_REQUIRED"] = 402] = "PAYMENT_REQUIRED";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.3
|
||||
///
|
||||
/// The client does not have access rights to the content, i.e. they are
|
||||
/// unauthorized, so server is rejecting to give proper response. Unlike 401,
|
||||
/// the client's identity is known to the server.
|
||||
StatusCodes[StatusCodes["FORBIDDEN"] = 403] = "FORBIDDEN";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.4
|
||||
///
|
||||
/// The server can not find requested resource. In the browser, this means
|
||||
/// the URL is not recognized. In an API, this can also mean that the endpoint
|
||||
/// is valid but the resource itself does not exist. Servers may also send this
|
||||
/// response instead of 403 to hide the existence of a resource from an
|
||||
/// unauthorized client. This response code is probably the most famous one due
|
||||
/// to its frequent occurence on the web.
|
||||
StatusCodes[StatusCodes["NOT_FOUND"] = 404] = "NOT_FOUND";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.5
|
||||
///
|
||||
/// The request method is known by the server but has been disabled and
|
||||
/// cannot be used. For example, an API may forbid DELETE-ing a resource. The
|
||||
/// two mandatory methods, GET and HEAD, must never be disabled and should not
|
||||
/// return this error code.
|
||||
StatusCodes[StatusCodes["METHOD_NOT_ALLOWED"] = 405] = "METHOD_NOT_ALLOWED";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.6
|
||||
///
|
||||
/// This response is sent when the web server, after performing server-driven
|
||||
/// content negotiation, doesn't find any content following the criteria given
|
||||
/// by the user agent.
|
||||
StatusCodes[StatusCodes["NOT_ACCEPTABLE"] = 406] = "NOT_ACCEPTABLE";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7235#section-3.2
|
||||
///
|
||||
/// This is similar to 401 but authentication is needed to be done by a proxy.
|
||||
StatusCodes[StatusCodes["PROXY_AUTHENTICATION_REQUIRED"] = 407] = "PROXY_AUTHENTICATION_REQUIRED";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.7
|
||||
///
|
||||
/// This response is sent on an idle connection by some servers, even without
|
||||
/// any previous request by the client. It means that the server would like to
|
||||
/// shut down this unused connection. This response is used much more since
|
||||
/// some browsers, like Chrome, Firefox 27+, or IE9, use HTTP pre-connection
|
||||
/// mechanisms to speed up surfing. Also note that some servers merely shut
|
||||
/// down the connection without sending this message.
|
||||
StatusCodes[StatusCodes["REQUEST_TIMEOUT"] = 408] = "REQUEST_TIMEOUT";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.8
|
||||
///
|
||||
/// This response is sent when a request conflicts with the current state of the server.
|
||||
StatusCodes[StatusCodes["CONFLICT"] = 409] = "CONFLICT";
|
||||
///
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.9
|
||||
///
|
||||
/// This response would be sent when the requested content has been
|
||||
/// permenantly deleted from server, with no forwarding address. Clients are
|
||||
/// expected to remove their caches and links to the resource. The HTTP
|
||||
/// specification intends this status code to be used for "limited-time,
|
||||
/// promotional services". APIs should not feel compelled to indicate resources
|
||||
/// that have been deleted with this status code.
|
||||
StatusCodes[StatusCodes["GONE"] = 410] = "GONE";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.10
|
||||
///
|
||||
/// The server rejected the request because the Content-Length header field
|
||||
/// is not defined and the server requires it.
|
||||
StatusCodes[StatusCodes["LENGTH_REQUIRED"] = 411] = "LENGTH_REQUIRED";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7232#section-4.2
|
||||
///
|
||||
/// The client has indicated preconditions in its headers which the server
|
||||
/// does not meet.
|
||||
StatusCodes[StatusCodes["PRECONDITION_FAILED"] = 412] = "PRECONDITION_FAILED";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.11
|
||||
///
|
||||
/// Request entity is larger than limits defined by server; the server might
|
||||
/// close the connection or return an Retry-After header field.
|
||||
StatusCodes[StatusCodes["REQUEST_TOO_LONG"] = 413] = "REQUEST_TOO_LONG";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.12
|
||||
///
|
||||
/// The URI requested by the client is longer than the server is willing to interpret.
|
||||
StatusCodes[StatusCodes["REQUEST_URI_TOO_LONG"] = 414] = "REQUEST_URI_TOO_LONG";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.13
|
||||
///
|
||||
/// The media format of the requested data is not supported by the server, so
|
||||
/// the server is rejecting the request.
|
||||
StatusCodes[StatusCodes["UNSUPPORTED_MEDIA_TYPE"] = 415] = "UNSUPPORTED_MEDIA_TYPE";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7233#section-4.4
|
||||
///
|
||||
/// The range specified by the Range header field in the request can't be
|
||||
/// fulfilled; it's possible that the range is outside the size of the target
|
||||
/// URI's data.
|
||||
StatusCodes[StatusCodes["REQUESTED_RANGE_NOT_SATISFIABLE"] = 416] = "REQUESTED_RANGE_NOT_SATISFIABLE";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.14
|
||||
///
|
||||
/// This response code means the expectation indicated by the Expect request
|
||||
/// header field can't be met by the server.
|
||||
StatusCodes[StatusCodes["EXPECTATION_FAILED"] = 417] = "EXPECTATION_FAILED";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc2324#section-2.3.2
|
||||
///
|
||||
/// Any attempt to brew coffee with a teapot should result in the error code
|
||||
/// "418 I'm a teapot". The resulting entity body MAY be short and stout.
|
||||
StatusCodes[StatusCodes["IM_A_TEAPOT"] = 418] = "IM_A_TEAPOT";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.6
|
||||
///
|
||||
/// The 507 (Insufficient Storage) status code means the method could not be
|
||||
/// performed on the resource because the server is unable to store the
|
||||
/// representation needed to successfully complete the request. This condition
|
||||
/// is considered to be temporary. If the request which received this status
|
||||
/// code was the result of a user action, the request MUST NOT be repeated
|
||||
/// until it is requested by a separate user action.
|
||||
StatusCodes[StatusCodes["INSUFFICIENT_SPACE_ON_RESOURCE"] = 419] = "INSUFFICIENT_SPACE_ON_RESOURCE";
|
||||
/// @deprecated
|
||||
/// Official Documentation @ https://tools.ietf.org/rfcdiff?difftype=--hwdiff&url2=draft-ietf-webdav-protocol-06.txt
|
||||
///
|
||||
/// A deprecated response used by the Spring Framework when a method has failed.
|
||||
StatusCodes[StatusCodes["METHOD_FAILURE"] = 420] = "METHOD_FAILURE";
|
||||
/// Official Documentation @ https://datatracker.ietf.org/doc/html/rfc7540#section-9.1.2
|
||||
///
|
||||
/// Defined in the specification of HTTP/2 to indicate that a server is not
|
||||
/// able to produce a response for the combination of scheme and authority that
|
||||
/// are included in the request URI.
|
||||
StatusCodes[StatusCodes["MISDIRECTED_REQUEST"] = 421] = "MISDIRECTED_REQUEST";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.3
|
||||
///
|
||||
/// The request was well-formed but was unable to be followed due to semantic errors.
|
||||
StatusCodes[StatusCodes["UNPROCESSABLE_ENTITY"] = 422] = "UNPROCESSABLE_ENTITY";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.4
|
||||
///
|
||||
/// The resource that is being accessed is locked.
|
||||
StatusCodes[StatusCodes["LOCKED"] = 423] = "LOCKED";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.5
|
||||
///
|
||||
/// The request failed due to failure of a previous request.
|
||||
StatusCodes[StatusCodes["FAILED_DEPENDENCY"] = 424] = "FAILED_DEPENDENCY";
|
||||
/// Official Documentation @ https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.15
|
||||
///
|
||||
/// The server refuses to perform the request using the current protocol but
|
||||
/// might be willing to do so after the client upgrades to a different
|
||||
/// protocol.
|
||||
StatusCodes[StatusCodes["UPGRADE_REQUIRED"] = 426] = "UPGRADE_REQUIRED";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc6585#section-3
|
||||
///
|
||||
/// The origin server requires the request to be conditional. Intended to
|
||||
/// prevent the 'lost update' problem, where a client GETs a resource's state,
|
||||
/// modifies it, and PUTs it back to the server, when meanwhile a third party
|
||||
/// has modified the state on the server, leading to a conflict.
|
||||
StatusCodes[StatusCodes["PRECONDITION_REQUIRED"] = 428] = "PRECONDITION_REQUIRED";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc6585#section-4
|
||||
///
|
||||
/// The user has sent too many requests in a given amount of time ("rate limiting").
|
||||
StatusCodes[StatusCodes["TOO_MANY_REQUESTS"] = 429] = "TOO_MANY_REQUESTS";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc6585#section-5
|
||||
///
|
||||
/// The server is unwilling to process the request because its header fields
|
||||
/// are too large. The request MAY be resubmitted after reducing the size of
|
||||
/// the request header fields.
|
||||
StatusCodes[StatusCodes["REQUEST_HEADER_FIELDS_TOO_LARGE"] = 431] = "REQUEST_HEADER_FIELDS_TOO_LARGE";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7725
|
||||
///
|
||||
/// The user-agent requested a resource that cannot legally be provided, such
|
||||
/// as a web page censored by a government.
|
||||
StatusCodes[StatusCodes["UNAVAILABLE_FOR_LEGAL_REASONS"] = 451] = "UNAVAILABLE_FOR_LEGAL_REASONS";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.1
|
||||
///
|
||||
/// The server encountered an unexpected condition that prevented it from
|
||||
/// fulfilling the request.
|
||||
StatusCodes[StatusCodes["INTERNAL_SERVER_ERROR"] = 500] = "INTERNAL_SERVER_ERROR";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.2
|
||||
///
|
||||
/// The request method is not supported by the server and cannot be handled.
|
||||
/// The only methods that servers are required to support (and therefore that
|
||||
/// must not return this code) are GET and HEAD.
|
||||
StatusCodes[StatusCodes["NOT_IMPLEMENTED"] = 501] = "NOT_IMPLEMENTED";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.3
|
||||
///
|
||||
/// This error response means that the server, while working as a gateway to
|
||||
/// get a response needed to handle the request, got an invalid response.
|
||||
StatusCodes[StatusCodes["BAD_GATEWAY"] = 502] = "BAD_GATEWAY";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.4
|
||||
///
|
||||
/// The server is not ready to handle the request. Common causes are a server
|
||||
/// that is down for maintenance or that is overloaded. Note that together with
|
||||
/// this response, a user-friendly page explaining the problem should be sent.
|
||||
/// This responses should be used for temporary conditions and the Retry-After:
|
||||
/// HTTP header should, if possible, contain the estimated time before the
|
||||
/// recovery of the service. The webmaster must also take care about the
|
||||
/// caching-related headers that are sent along with this response, as these
|
||||
/// temporary condition responses should usually not be cached.
|
||||
StatusCodes[StatusCodes["SERVICE_UNAVAILABLE"] = 503] = "SERVICE_UNAVAILABLE";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.5
|
||||
///
|
||||
/// This error response is given when the server is acting as a gateway and
|
||||
/// cannot get a response in time.
|
||||
StatusCodes[StatusCodes["GATEWAY_TIMEOUT"] = 504] = "GATEWAY_TIMEOUT";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.6
|
||||
///
|
||||
/// The HTTP version used in the request is not supported by the server.
|
||||
StatusCodes[StatusCodes["HTTP_VERSION_NOT_SUPPORTED"] = 505] = "HTTP_VERSION_NOT_SUPPORTED";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.6
|
||||
///
|
||||
/// The server has an internal configuration error: the chosen variant
|
||||
/// resource is configured to engage in transparent content negotiation itself,
|
||||
/// and is therefore not a proper end point in the negotiation process.
|
||||
StatusCodes[StatusCodes["INSUFFICIENT_STORAGE"] = 507] = "INSUFFICIENT_STORAGE";
|
||||
/// Official Documentation @ https://tools.ietf.org/html/rfc6585#section-6
|
||||
///
|
||||
/// The 511 status code indicates that the client needs to authenticate to
|
||||
/// gain network access.
|
||||
StatusCodes[StatusCodes["NETWORK_AUTHENTICATION_REQUIRED"] = 511] = "NETWORK_AUTHENTICATION_REQUIRED";
|
||||
})(StatusCodes || (StatusCodes = {}));
|
||||
export 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) : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
export function stringHandler(f) {
|
||||
return async (req) => {
|
||||
try {
|
||||
let body = req.body;
|
||||
let resp = await f({
|
||||
uri: req.uri,
|
||||
params: req.params,
|
||||
headers: req.headers,
|
||||
user: req.user,
|
||||
body: body && decodeFallback(body),
|
||||
});
|
||||
if (resp === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof resp === "string") {
|
||||
return {
|
||||
status: StatusCodes.OK,
|
||||
body: encodeFallback(resp),
|
||||
};
|
||||
}
|
||||
const respBody = resp.body;
|
||||
return {
|
||||
headers: resp.headers,
|
||||
status: resp.status,
|
||||
body: respBody ? encodeFallback(respBody) : undefined,
|
||||
};
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof HttpError) {
|
||||
return err.toResponse();
|
||||
}
|
||||
return {
|
||||
status: StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
body: encodeFallback(`Uncaught error: ${err}`),
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
export function htmlHandler(f) {
|
||||
return async (req) => {
|
||||
try {
|
||||
let body = req.body;
|
||||
let resp = await f({
|
||||
uri: req.uri,
|
||||
params: req.params,
|
||||
headers: req.headers,
|
||||
user: req.user,
|
||||
body: body && decodeFallback(body),
|
||||
});
|
||||
if (resp === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof resp === "string") {
|
||||
return {
|
||||
headers: [["content-type", "text/html"]],
|
||||
status: StatusCodes.OK,
|
||||
body: encodeFallback(resp),
|
||||
};
|
||||
}
|
||||
const respBody = resp.body;
|
||||
return {
|
||||
headers: [["content-type", "text/html"], ...(resp.headers ?? [])],
|
||||
status: resp.status,
|
||||
body: respBody ? encodeFallback(respBody) : undefined,
|
||||
};
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof HttpError) {
|
||||
return err.toResponse();
|
||||
}
|
||||
return {
|
||||
status: StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
body: encodeFallback(`Uncaught error: ${err}`),
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
export function jsonHandler(f) {
|
||||
return async (req) => {
|
||||
try {
|
||||
let body = req.body;
|
||||
let resp = await f({
|
||||
uri: req.uri,
|
||||
params: req.params,
|
||||
headers: req.headers,
|
||||
user: req.user,
|
||||
body: body && decodeFallback(body),
|
||||
});
|
||||
if (resp === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
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)) : undefined,
|
||||
};
|
||||
}
|
||||
return {
|
||||
headers: [["content-type", "application/json"]],
|
||||
status: StatusCodes.OK,
|
||||
body: encodeFallback(JSON.stringify(resp)),
|
||||
};
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof HttpError) {
|
||||
return err.toResponse();
|
||||
}
|
||||
return {
|
||||
headers: [["content-type", "application/json"]],
|
||||
status: StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
body: encodeFallback(`Uncaught error: ${err}`),
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
const callbacks = new Map();
|
||||
export function addRoute(method, route, callback) {
|
||||
rustyscript.functions.route(method, route);
|
||||
callbacks.set(`${method}:${route}`, callback);
|
||||
console.debug("JS: Added route:", method, route);
|
||||
}
|
||||
export async function dispatch(method, route, uri, pathParams, headers, user, body) {
|
||||
const key = `${method}:${route}`;
|
||||
const cb = callbacks.get(key);
|
||||
if (!cb) {
|
||||
throw Error(`Missing callback: ${key}`);
|
||||
}
|
||||
return ((await cb({
|
||||
uri,
|
||||
params: Object.fromEntries(pathParams),
|
||||
headers: Object.fromEntries(headers),
|
||||
user: user,
|
||||
body,
|
||||
})) ?? { status: StatusCodes.OK });
|
||||
}
|
||||
globalThis.__dispatch = dispatch;
|
||||
export async function query(queryStr, params) {
|
||||
return await rustyscript.async_functions.query(queryStr, params);
|
||||
}
|
||||
export async function execute(queryStr, params) {
|
||||
return await rustyscript.async_functions.execute(queryStr, params);
|
||||
}
|
||||
export 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(),
|
||||
};
|
||||
}
|
||||
/// @param {Uint8Array} bytes
|
||||
/// @return {string}
|
||||
///
|
||||
/// source: https://github.com/samthor/fast-text-encoding
|
||||
export function decodeFallback(bytes) {
|
||||
var inputIndex = 0;
|
||||
// Create a working buffer for UTF-16 code points, but don't generate one
|
||||
// which is too large for small input sizes. UTF-8 to UCS-16 conversion is
|
||||
// going to be at most 1:1, if all code points are ASCII. The other extreme
|
||||
// is 4-byte UTF-8, which results in two UCS-16 points, but this is still 50%
|
||||
// fewer entries in the output.
|
||||
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 there's no more data or there'd be no room for two UTF-16 values,
|
||||
// create a chunk. This isn't done at the end by simply slicing the data
|
||||
// into equal sized chunks as we might hit a surrogate pair.
|
||||
if (!more || pendingIndex >= pendingSize - 1) {
|
||||
// nb. .apply and friends are *really slow*. Low-hanging fruit is to
|
||||
// expand this to literally pass pending[0], pending[1], ... etc, but
|
||||
// the output code expands pretty fast in this case.
|
||||
// These extra vars get compiled out: they're just to make TS happy.
|
||||
// Turns out you can pass an ArrayLike to .apply().
|
||||
var subarray = pending.subarray(0, pendingIndex);
|
||||
var arraylike = subarray;
|
||||
chunks.push(String.fromCharCode.apply(null, arraylike));
|
||||
if (!more) {
|
||||
return chunks.join("");
|
||||
}
|
||||
// Move the buffer forward and create another chunk.
|
||||
bytes = bytes.subarray(inputIndex);
|
||||
inputIndex = 0;
|
||||
pendingIndex = 0;
|
||||
}
|
||||
// The native TextDecoder will generate "REPLACEMENT CHARACTER" where the
|
||||
// input data is invalid. Here, we blindly parse the data even if it's
|
||||
// wrong: e.g., if a 3-byte sequence doesn't have two valid continuations.
|
||||
var byte1 = bytes[inputIndex++];
|
||||
if ((byte1 & 0x80) === 0) {
|
||||
// 1-byte or null
|
||||
pending[pendingIndex++] = byte1;
|
||||
}
|
||||
else if ((byte1 & 0xe0) === 0xc0) {
|
||||
// 2-byte
|
||||
var byte2 = bytes[inputIndex++] & 0x3f;
|
||||
pending[pendingIndex++] = ((byte1 & 0x1f) << 6) | byte2;
|
||||
}
|
||||
else if ((byte1 & 0xf0) === 0xe0) {
|
||||
// 3-byte
|
||||
var byte2 = bytes[inputIndex++] & 0x3f;
|
||||
var byte3 = bytes[inputIndex++] & 0x3f;
|
||||
pending[pendingIndex++] = ((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3;
|
||||
}
|
||||
else if ((byte1 & 0xf8) === 0xf0) {
|
||||
// 4-byte
|
||||
var byte2 = bytes[inputIndex++] & 0x3f;
|
||||
var byte3 = bytes[inputIndex++] & 0x3f;
|
||||
var byte4 = bytes[inputIndex++] & 0x3f;
|
||||
// this can be > 0xffff, so possibly generate surrogates
|
||||
var codepoint = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4;
|
||||
if (codepoint > 0xffff) {
|
||||
// codepoint &= ~0x10000;
|
||||
codepoint -= 0x10000;
|
||||
pending[pendingIndex++] = ((codepoint >>> 10) & 0x3ff) | 0xd800;
|
||||
codepoint = 0xdc00 | (codepoint & 0x3ff);
|
||||
}
|
||||
pending[pendingIndex++] = codepoint;
|
||||
}
|
||||
else {
|
||||
// invalid initial byte
|
||||
}
|
||||
}
|
||||
}
|
||||
/// @param {string} string
|
||||
/// @return {Uint8Array}
|
||||
////
|
||||
/// source: https://github.com/samthor/fast-text-encoding
|
||||
export function encodeFallback(string) {
|
||||
var pos = 0;
|
||||
var len = string.length;
|
||||
var at = 0; // output position
|
||||
var tlen = Math.max(32, len + (len >>> 1) + 7); // 1.5x size
|
||||
var target = new Uint8Array((tlen >>> 3) << 3); // ... but at 8 byte offset
|
||||
while (pos < len) {
|
||||
var value = string.charCodeAt(pos++);
|
||||
if (value >= 0xd800 && value <= 0xdbff) {
|
||||
// high surrogate
|
||||
if (pos < len) {
|
||||
var extra = string.charCodeAt(pos);
|
||||
if ((extra & 0xfc00) === 0xdc00) {
|
||||
++pos;
|
||||
value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000;
|
||||
}
|
||||
}
|
||||
if (value >= 0xd800 && value <= 0xdbff) {
|
||||
continue; // drop lone surrogate
|
||||
}
|
||||
}
|
||||
// expand the buffer if we couldn't write 4 bytes
|
||||
if (at + 4 > target.length) {
|
||||
tlen += 8; // minimum extra
|
||||
tlen *= 1.0 + (pos / string.length) * 2; // take 2x the remaining
|
||||
tlen = (tlen >>> 3) << 3; // 8 byte offset
|
||||
var update = new Uint8Array(tlen);
|
||||
update.set(target);
|
||||
target = update;
|
||||
}
|
||||
if ((value & 0xffffff80) === 0) {
|
||||
// 1-byte
|
||||
target[at++] = value; // ASCII
|
||||
continue;
|
||||
}
|
||||
else if ((value & 0xfffff800) === 0) {
|
||||
// 2-byte
|
||||
target[at++] = ((value >>> 6) & 0x1f) | 0xc0;
|
||||
}
|
||||
else if ((value & 0xffff0000) === 0) {
|
||||
// 3-byte
|
||||
target[at++] = ((value >>> 12) & 0x0f) | 0xe0;
|
||||
target[at++] = ((value >>> 6) & 0x3f) | 0x80;
|
||||
}
|
||||
else if ((value & 0xffe00000) === 0) {
|
||||
// 4-byte
|
||||
target[at++] = ((value >>> 18) & 0x07) | 0xf0;
|
||||
target[at++] = ((value >>> 12) & 0x3f) | 0x80;
|
||||
target[at++] = ((value >>> 6) & 0x3f) | 0x80;
|
||||
}
|
||||
else {
|
||||
continue; // out of range
|
||||
}
|
||||
target[at++] = (value & 0x3f) | 0x80;
|
||||
}
|
||||
// Use subarray if slice isn't supported (IE11). This will use more memory
|
||||
// because the original array still exists.
|
||||
return target.slice ? target.slice(0, at) : target.subarray(0, at);
|
||||
}
|
||||
26
examples/coffeesearch/tsconfig.json
Normal file
26
examples/coffeesearch/tsconfig.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "Bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
7
examples/coffeesearch/vite.config.ts
Normal file
7
examples/coffeesearch/vite.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||
1
examples/custom-binary/.gitignore
vendored
Normal file
1
examples/custom-binary/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
traildepot/
|
||||
5
examples/tutorial/traildepot/.gitignore
vendored
Normal file
5
examples/tutorial/traildepot/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
backups/
|
||||
data/
|
||||
secrets/
|
||||
uploads/
|
||||
164
pnpm-lock.yaml
generated
164
pnpm-lock.yaml
generated
@@ -177,6 +177,52 @@ importers:
|
||||
specifier: ^0.33.5
|
||||
version: 0.33.5
|
||||
|
||||
examples/coffeesearch:
|
||||
dependencies:
|
||||
react:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1
|
||||
react-dom:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1(react@18.3.1)
|
||||
devDependencies:
|
||||
'@eslint/js':
|
||||
specifier: ^9.13.0
|
||||
version: 9.14.0
|
||||
'@types/react':
|
||||
specifier: ^18.3.12
|
||||
version: 18.3.12
|
||||
'@types/react-dom':
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(vite@5.4.11(@types/node@22.9.0))
|
||||
eslint:
|
||||
specifier: ^9.13.0
|
||||
version: 9.14.0(jiti@2.4.0)
|
||||
eslint-plugin-react-hooks:
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0(eslint@9.14.0(jiti@2.4.0))
|
||||
eslint-plugin-react-refresh:
|
||||
specifier: ^0.4.14
|
||||
version: 0.4.14(eslint@9.14.0(jiti@2.4.0))
|
||||
globals:
|
||||
specifier: ^15.11.0
|
||||
version: 15.12.0
|
||||
prettier:
|
||||
specifier: ^3.3.3
|
||||
version: 3.3.3
|
||||
typescript:
|
||||
specifier: ~5.6.2
|
||||
version: 5.6.3
|
||||
typescript-eslint:
|
||||
specifier: ^8.11.0
|
||||
version: 8.14.0(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3)
|
||||
vite:
|
||||
specifier: ^5.4.10
|
||||
version: 5.4.11(@types/node@22.9.0)
|
||||
|
||||
examples/tutorial/scripts:
|
||||
dependencies:
|
||||
csv-parse:
|
||||
@@ -610,6 +656,18 @@ packages:
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/plugin-transform-react-jsx-self@7.25.9':
|
||||
resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/plugin-transform-react-jsx-source@7.25.9':
|
||||
resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/plugin-transform-react-jsx@7.25.9':
|
||||
resolution: {integrity: sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -1548,6 +1606,15 @@ packages:
|
||||
'@types/picomatch@2.3.3':
|
||||
resolution: {integrity: sha512-Yll76ZHikRFCyz/pffKGjrCwe/le2CDwOP5F210KQo27kpRE46U2rDnzikNlVn6/ezH3Mhn46bJMTfeVTtcYMg==}
|
||||
|
||||
'@types/prop-types@15.7.13':
|
||||
resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==}
|
||||
|
||||
'@types/react-dom@18.3.1':
|
||||
resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==}
|
||||
|
||||
'@types/react@18.3.12':
|
||||
resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==}
|
||||
|
||||
'@types/sax@1.2.7':
|
||||
resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==}
|
||||
|
||||
@@ -1626,6 +1693,12 @@ packages:
|
||||
'@ungap/structured-clone@1.2.0':
|
||||
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
|
||||
|
||||
'@vitejs/plugin-react@4.3.3':
|
||||
resolution: {integrity: sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
peerDependencies:
|
||||
vite: ^4.2.0 || ^5.0.0
|
||||
|
||||
'@vitest/expect@2.1.4':
|
||||
resolution: {integrity: sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==}
|
||||
|
||||
@@ -2242,6 +2315,17 @@ packages:
|
||||
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
eslint-plugin-react-hooks@5.0.0:
|
||||
resolution: {integrity: sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
|
||||
|
||||
eslint-plugin-react-refresh@0.4.14:
|
||||
resolution: {integrity: sha512-aXvzCTK7ZBv1e7fahFuR3Z/fyQQSIQ711yPgYRj+Oj64tyTgO4iQIDmYXDBqvSWQ/FA4OSCsXOStlF+noU0/NA==}
|
||||
peerDependencies:
|
||||
eslint: '>=7'
|
||||
|
||||
eslint-scope@8.2.0:
|
||||
resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -2919,6 +3003,10 @@ packages:
|
||||
longest-streak@3.1.0:
|
||||
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
|
||||
|
||||
loose-envify@1.4.0:
|
||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||
hasBin: true
|
||||
|
||||
loupe@3.1.2:
|
||||
resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==}
|
||||
|
||||
@@ -3523,6 +3611,19 @@ packages:
|
||||
engines: {node: '>=18.12.0'}
|
||||
hasBin: true
|
||||
|
||||
react-dom@18.3.1:
|
||||
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
|
||||
peerDependencies:
|
||||
react: ^18.3.1
|
||||
|
||||
react-refresh@0.14.2:
|
||||
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
react@18.3.1:
|
||||
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
read-cache@1.0.0:
|
||||
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
|
||||
|
||||
@@ -3679,6 +3780,9 @@ packages:
|
||||
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
|
||||
engines: {node: '>=v12.22.7'}
|
||||
|
||||
scheduler@0.23.2:
|
||||
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
|
||||
|
||||
section-matter@1.0.0:
|
||||
resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -4851,6 +4955,16 @@ snapshots:
|
||||
'@babel/helper-plugin-utils': 7.25.9
|
||||
optional: true
|
||||
|
||||
'@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.0)':
|
||||
dependencies:
|
||||
'@babel/core': 7.26.0
|
||||
'@babel/helper-plugin-utils': 7.25.9
|
||||
|
||||
'@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.0)':
|
||||
dependencies:
|
||||
'@babel/core': 7.26.0
|
||||
'@babel/helper-plugin-utils': 7.25.9
|
||||
|
||||
'@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0)':
|
||||
dependencies:
|
||||
'@babel/core': 7.26.0
|
||||
@@ -5813,6 +5927,17 @@ snapshots:
|
||||
|
||||
'@types/picomatch@2.3.3': {}
|
||||
|
||||
'@types/prop-types@15.7.13': {}
|
||||
|
||||
'@types/react-dom@18.3.1':
|
||||
dependencies:
|
||||
'@types/react': 18.3.12
|
||||
|
||||
'@types/react@18.3.12':
|
||||
dependencies:
|
||||
'@types/prop-types': 15.7.13
|
||||
csstype: 3.1.3
|
||||
|
||||
'@types/sax@1.2.7':
|
||||
dependencies:
|
||||
'@types/node': 17.0.45
|
||||
@@ -5916,6 +6041,17 @@ snapshots:
|
||||
|
||||
'@ungap/structured-clone@1.2.0': {}
|
||||
|
||||
'@vitejs/plugin-react@4.3.3(vite@5.4.11(@types/node@22.9.0))':
|
||||
dependencies:
|
||||
'@babel/core': 7.26.0
|
||||
'@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0)
|
||||
'@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0)
|
||||
'@types/babel__core': 7.20.5
|
||||
react-refresh: 0.14.2
|
||||
vite: 5.4.11(@types/node@22.9.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitest/expect@2.1.4':
|
||||
dependencies:
|
||||
'@vitest/spy': 2.1.4
|
||||
@@ -6728,6 +6864,14 @@ snapshots:
|
||||
|
||||
escape-string-regexp@5.0.0: {}
|
||||
|
||||
eslint-plugin-react-hooks@5.0.0(eslint@9.14.0(jiti@2.4.0)):
|
||||
dependencies:
|
||||
eslint: 9.14.0(jiti@2.4.0)
|
||||
|
||||
eslint-plugin-react-refresh@0.4.14(eslint@9.14.0(jiti@2.4.0)):
|
||||
dependencies:
|
||||
eslint: 9.14.0(jiti@2.4.0)
|
||||
|
||||
eslint-scope@8.2.0:
|
||||
dependencies:
|
||||
esrecurse: 4.3.0
|
||||
@@ -7535,6 +7679,10 @@ snapshots:
|
||||
|
||||
longest-streak@3.1.0: {}
|
||||
|
||||
loose-envify@1.4.0:
|
||||
dependencies:
|
||||
js-tokens: 4.0.0
|
||||
|
||||
loupe@3.1.2: {}
|
||||
|
||||
lru-cache@10.4.3: {}
|
||||
@@ -8460,6 +8608,18 @@ snapshots:
|
||||
- '@swc/wasm'
|
||||
- encoding
|
||||
|
||||
react-dom@18.3.1(react@18.3.1):
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
react: 18.3.1
|
||||
scheduler: 0.23.2
|
||||
|
||||
react-refresh@0.14.2: {}
|
||||
|
||||
react@18.3.1:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
|
||||
read-cache@1.0.0:
|
||||
dependencies:
|
||||
pify: 2.3.0
|
||||
@@ -8713,6 +8873,10 @@ snapshots:
|
||||
dependencies:
|
||||
xmlchars: 2.2.0
|
||||
|
||||
scheduler@0.23.2:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
|
||||
section-matter@1.0.0:
|
||||
dependencies:
|
||||
extend-shallow: 2.0.1
|
||||
|
||||
@@ -4,6 +4,7 @@ packages:
|
||||
- 'ui/admin'
|
||||
- 'ui/auth'
|
||||
- 'examples/blog/web'
|
||||
- 'examples/coffeesearch'
|
||||
- 'examples/tutorial/scripts'
|
||||
- 'trailbase-core/js'
|
||||
options:
|
||||
|
||||
Reference in New Issue
Block a user