mirror of
https://github.com/trailbaseio/trailbase.git
synced 2025-12-20 08:59:44 -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
|
# Dev artifacts
|
||||||
public/
|
public/
|
||||||
traildepot/
|
|
||||||
|
|||||||
3
examples/blog/traildepot/.gitignore
vendored
3
examples/blog/traildepot/.gitignore
vendored
@@ -5,3 +5,6 @@ secrets/
|
|||||||
uploads/
|
uploads/
|
||||||
|
|
||||||
!migrations/
|
!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
|
specifier: ^0.33.5
|
||||||
version: 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:
|
examples/tutorial/scripts:
|
||||||
dependencies:
|
dependencies:
|
||||||
csv-parse:
|
csv-parse:
|
||||||
@@ -610,6 +656,18 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@babel/core': ^7.0.0-0
|
'@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':
|
'@babel/plugin-transform-react-jsx@7.25.9':
|
||||||
resolution: {integrity: sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==}
|
resolution: {integrity: sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -1548,6 +1606,15 @@ packages:
|
|||||||
'@types/picomatch@2.3.3':
|
'@types/picomatch@2.3.3':
|
||||||
resolution: {integrity: sha512-Yll76ZHikRFCyz/pffKGjrCwe/le2CDwOP5F210KQo27kpRE46U2rDnzikNlVn6/ezH3Mhn46bJMTfeVTtcYMg==}
|
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':
|
'@types/sax@1.2.7':
|
||||||
resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==}
|
resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==}
|
||||||
|
|
||||||
@@ -1626,6 +1693,12 @@ packages:
|
|||||||
'@ungap/structured-clone@1.2.0':
|
'@ungap/structured-clone@1.2.0':
|
||||||
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
|
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':
|
'@vitest/expect@2.1.4':
|
||||||
resolution: {integrity: sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==}
|
resolution: {integrity: sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==}
|
||||||
|
|
||||||
@@ -2242,6 +2315,17 @@ packages:
|
|||||||
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
|
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
|
||||||
engines: {node: '>=12'}
|
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:
|
eslint-scope@8.2.0:
|
||||||
resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==}
|
resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@@ -2919,6 +3003,10 @@ packages:
|
|||||||
longest-streak@3.1.0:
|
longest-streak@3.1.0:
|
||||||
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
|
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:
|
loupe@3.1.2:
|
||||||
resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==}
|
resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==}
|
||||||
|
|
||||||
@@ -3523,6 +3611,19 @@ packages:
|
|||||||
engines: {node: '>=18.12.0'}
|
engines: {node: '>=18.12.0'}
|
||||||
hasBin: true
|
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:
|
read-cache@1.0.0:
|
||||||
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
|
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
|
||||||
|
|
||||||
@@ -3679,6 +3780,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
|
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
|
||||||
engines: {node: '>=v12.22.7'}
|
engines: {node: '>=v12.22.7'}
|
||||||
|
|
||||||
|
scheduler@0.23.2:
|
||||||
|
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
|
||||||
|
|
||||||
section-matter@1.0.0:
|
section-matter@1.0.0:
|
||||||
resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
|
resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -4851,6 +4955,16 @@ snapshots:
|
|||||||
'@babel/helper-plugin-utils': 7.25.9
|
'@babel/helper-plugin-utils': 7.25.9
|
||||||
optional: true
|
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)':
|
'@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.26.0
|
'@babel/core': 7.26.0
|
||||||
@@ -5813,6 +5927,17 @@ snapshots:
|
|||||||
|
|
||||||
'@types/picomatch@2.3.3': {}
|
'@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':
|
'@types/sax@1.2.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 17.0.45
|
'@types/node': 17.0.45
|
||||||
@@ -5916,6 +6041,17 @@ snapshots:
|
|||||||
|
|
||||||
'@ungap/structured-clone@1.2.0': {}
|
'@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':
|
'@vitest/expect@2.1.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/spy': 2.1.4
|
'@vitest/spy': 2.1.4
|
||||||
@@ -6728,6 +6864,14 @@ snapshots:
|
|||||||
|
|
||||||
escape-string-regexp@5.0.0: {}
|
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:
|
eslint-scope@8.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
esrecurse: 4.3.0
|
esrecurse: 4.3.0
|
||||||
@@ -7535,6 +7679,10 @@ snapshots:
|
|||||||
|
|
||||||
longest-streak@3.1.0: {}
|
longest-streak@3.1.0: {}
|
||||||
|
|
||||||
|
loose-envify@1.4.0:
|
||||||
|
dependencies:
|
||||||
|
js-tokens: 4.0.0
|
||||||
|
|
||||||
loupe@3.1.2: {}
|
loupe@3.1.2: {}
|
||||||
|
|
||||||
lru-cache@10.4.3: {}
|
lru-cache@10.4.3: {}
|
||||||
@@ -8460,6 +8608,18 @@ snapshots:
|
|||||||
- '@swc/wasm'
|
- '@swc/wasm'
|
||||||
- encoding
|
- 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:
|
read-cache@1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
pify: 2.3.0
|
pify: 2.3.0
|
||||||
@@ -8713,6 +8873,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
xmlchars: 2.2.0
|
xmlchars: 2.2.0
|
||||||
|
|
||||||
|
scheduler@0.23.2:
|
||||||
|
dependencies:
|
||||||
|
loose-envify: 1.4.0
|
||||||
|
|
||||||
section-matter@1.0.0:
|
section-matter@1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
extend-shallow: 2.0.1
|
extend-shallow: 2.0.1
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ packages:
|
|||||||
- 'ui/admin'
|
- 'ui/admin'
|
||||||
- 'ui/auth'
|
- 'ui/auth'
|
||||||
- 'examples/blog/web'
|
- 'examples/blog/web'
|
||||||
|
- 'examples/coffeesearch'
|
||||||
- 'examples/tutorial/scripts'
|
- 'examples/tutorial/scripts'
|
||||||
- 'trailbase-core/js'
|
- 'trailbase-core/js'
|
||||||
options:
|
options:
|
||||||
|
|||||||
Reference in New Issue
Block a user