Minor: Write out js runtime to traildepot and rely on relative includes. Also some opportunistic cleanups along the way.

This commit is contained in:
Sebastian Jeltsch
2024-11-14 10:46:39 +01:00
parent 7465203174
commit 8fa26c246f
11 changed files with 927 additions and 139 deletions

View File

@@ -7,12 +7,14 @@ format:
pnpm -r format; \
cargo +nightly fmt; \
dart format client/trailbase-dart/ examples/blog/flutter/; \
txtpbfmt `find . -regex ".*.textproto"`
txtpbfmt `find . -regex ".*.textproto"`; \
dotnet format client/trailbase-dotnet
check:
pnpm -r check; \
cargo clippy --workspace --no-deps; \
dart analyze client/trailbase-dart examples/blog/flutter
dart analyze client/trailbase-dart examples/blog/flutter; \
dotnet format client/trailbase-dotnet --verify-no-changes
docker:
docker build . -t trailbase/trailbase

View File

@@ -1,8 +1,8 @@
import { addRoute, parsePath, query, htmlHandler, jsonHandler, stringHandler } from "trailbase:main";
import type { JsonRequestType, ParsedPath, StringRequestType } from "../../../trailbase-core/js/src/index.ts";
import { addRoute, parsePath, query, htmlHandler, jsonHandler, stringHandler } from "../trailbase.js";
import type { JsonRequestType, ParsedPath, StringRequestType } from "../trailbase.d.ts";
addRoute("GET", "/test", stringHandler(async (req: StringRequestType) => {
const uri : ParsedPath = parsePath(req.uri);
const uri: ParsedPath = parsePath(req.uri);
const table = uri.query.get("table");
if (table) {

120
client/testfixture/trailbase.d.ts vendored Normal file
View File

@@ -0,0 +1,120 @@
export type HeaderMapType = {
[key: string]: string;
};
export type PathParamsType = {
[key: string]: string;
};
export type RequestType = {
uri: string;
params: PathParamsType;
headers: HeaderMapType;
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 type StringRequestType = {
uri: string;
params: PathParamsType;
headers: HeaderMapType;
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;
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][], 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;

View File

@@ -0,0 +1,632 @@
/// 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 function stringHandler(f) {
return async (req) => {
try {
let body = req.body;
let resp = await f({
uri: req.uri,
params: req.params,
headers: req.headers,
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) {
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,
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) {
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,
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) {
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, 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),
body,
})) ?? { status: StatusCodes.OK });
}
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);
}
globalThis.__dispatch = dispatch;

View File

@@ -24,10 +24,18 @@ use trailbase_cli::{
type BoxError = Box<dyn std::error::Error>;
fn init_logger(dev: bool) {
// SWC is very spammy in in debug builds and complaints about source maps when compiling
// typescript to javascript. Since we don't care about source maps and didn't find a better
// option to mute the errors, turn it off in debug builds.
#[cfg(debug_assertions)]
const DEFAULT: &str = "info,refinery_core=warn,tracing::span=warn,swc_ecma_codegen=off";
#[cfg(not(debug_assertions))]
const DEFAULT: &str = "info,refinery_core=warn,tracing::span=warn";
env_logger::init_from_env(if dev {
env_logger::Env::new().default_filter_or("info,trailbase_core=debug,refinery_core=warn")
env_logger::Env::new().default_filter_or(format!("{DEFAULT},trailbase_core=debug"))
} else {
env_logger::Env::new().default_filter_or("info,refinery_core=warn")
env_logger::Env::new().default_filter_or(DEFAULT)
});
}
@@ -279,7 +287,7 @@ async fn async_main(runtime: Rc<tokio::runtime::Runtime>) -> Result<(), BoxError
let (to, subject, body) = (cmd.to.clone(), cmd.subject.clone(), cmd.body.clone());
let (_new_db, state) = init_app_state(DataDir(args.data_dir), None, false, runtime).await?;
let (_new_db, state) = init_app_state(DataDir(args.data_dir), None, false).await?;
let email = Email::new(&state, to, subject, body)?;
email.send().await?;

View File

@@ -2,7 +2,6 @@ use libsql::Connection;
use log::*;
use object_store::ObjectStore;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;
use crate::auth::jwt::JwtHelper;
@@ -55,9 +54,6 @@ pub(crate) struct AppStateArgs {
pub conn: Connection,
pub logs_conn: Connection,
pub jwt: JwtHelper,
#[allow(unused)]
pub tokio_runtime: Rc<tokio::runtime::Runtime>,
}
#[derive(Clone)]
@@ -274,8 +270,8 @@ impl AppState {
.await;
}
pub(crate) fn script_runtime(&self) -> &RuntimeHandle {
return &self.state.runtime;
pub(crate) fn script_runtime(&self) -> RuntimeHandle {
return self.state.runtime.clone();
}
}

View File

@@ -1,41 +1,33 @@
use rust_embed::RustEmbed;
use rustyscript::deno_core::{
anyhow::Error, ModuleSource, ModuleSourceCode, ModuleSpecifier, RequestedModuleType,
ResolutionKind,
anyhow::{anyhow, Error},
ModuleSpecifier, RequestedModuleType, ResolutionKind,
};
use rustyscript::module_loader::ImportProvider;
use std::collections::HashMap;
use crate::assets::cow_to_string;
#[derive(Default)]
pub(crate) struct MemoryCache {
cache: HashMap<String, String>,
}
pub(crate) struct ImportProviderImpl;
impl MemoryCache {
/// Set a module in the cache
pub fn set(&mut self, specifier: &str, source: String) {
self.cache.insert(specifier.to_string(), source);
}
/// Get a module from the cache
pub fn get(&self, specifier: &ModuleSpecifier) -> Option<String> {
self.cache.get(specifier.as_str()).cloned()
}
pub fn has(&self, specifier: &ModuleSpecifier) -> bool {
self.cache.contains_key(specifier.as_str())
}
}
impl ImportProvider for MemoryCache {
impl ImportProvider for ImportProviderImpl {
fn resolve(
&mut self,
specifier: &ModuleSpecifier,
_referrer: &str,
_kind: ResolutionKind,
) -> Option<Result<ModuleSpecifier, Error>> {
// println!("resolve: {specifier:?}");
// Tell the loader to allow the import if the module is in the cache
self.get(specifier).map(|_| Ok(specifier.clone()))
log::trace!("resolve: {specifier:?}");
// Specifier is just a URL.
match specifier.scheme() {
"file" | "trailbase" => {
return Some(Ok(specifier.clone()));
}
scheme => {
return Some(Err(anyhow!("Unsupported schema: '{scheme}'")));
}
};
}
fn import(
@@ -45,27 +37,23 @@ impl ImportProvider for MemoryCache {
_is_dyn_import: bool,
_requested_module_type: RequestedModuleType,
) -> Option<Result<String, Error>> {
// println!("import : {specifier:?}");
// Return the source code if the module is in the cache
self.get(specifier).map(Ok)
}
log::trace!("import: {specifier:?}");
fn post_process(
&mut self,
specifier: &ModuleSpecifier,
source: ModuleSource,
) -> Result<ModuleSource, Error> {
log::debug!("post_process: {specifier:?}");
// Cache the source code
if !self.has(specifier) {
match &source.code {
ModuleSourceCode::String(s) => {
self.set(specifier.as_str(), s.to_string());
}
ModuleSourceCode::Bytes(_) => {}
match specifier.scheme() {
"trailbase" => {
return Some(Ok(cow_to_string(
JsRuntimeAssets::get("index.js")
.expect("Failed to read rt/index.js")
.data,
)));
}
_ => {
return None;
}
}
Ok(source)
}
}
#[derive(RustEmbed, Clone)]
#[folder = "js/dist/"]
pub(crate) struct JsRuntimeAssets;

View File

@@ -5,7 +5,6 @@ use axum::response::{IntoResponse, Response};
use axum::Router;
use libsql::Connection;
use parking_lot::Mutex;
use rust_embed::RustEmbed;
use rustyscript::{init_platform, json_args, Module, Runtime};
use serde::Deserialize;
use serde_json::from_value;
@@ -15,8 +14,9 @@ use std::sync::{Arc, LazyLock};
use thiserror::Error;
use crate::assets::cow_to_string;
use crate::js::import_provider::JsRuntimeAssets;
use crate::records::sql_to_json::rows_to_json_arrays;
use crate::AppState;
use crate::{AppState, DataDir};
mod import_provider;
@@ -63,6 +63,8 @@ impl RuntimeSingleton {
|x| x.get(),
);
log::info!("Starting v8 JavaScript runtime with {n_threads} workers.");
let (shared_sender, shared_receiver) = crossbeam_channel::unbounded::<Message>();
let (state, receivers): (Vec<State>, Vec<crossbeam_channel::Receiver<Message>>) = (0
@@ -89,7 +91,12 @@ impl RuntimeSingleton {
let shared_receiver = shared_receiver.clone();
return std::thread::spawn(move || {
let mut runtime = Self::init_runtime(index).unwrap();
let mut runtime = match Self::init_runtime(index) {
Ok(runtime) => runtime,
Err(err) => {
panic!("Failed to init v8 runtime on thread {index}: {err}");
}
};
loop {
crossbeam_channel::select! {
@@ -140,18 +147,11 @@ impl RuntimeSingleton {
.enable_time()
.enable_io()
.thread_name("v8-runtime")
.thread_stack_size(4 * 1024 * 1024)
.build()?;
let mut cache = import_provider::MemoryCache::default();
cache.set(
"trailbase:main",
cow_to_string(JsRuntimeAssets::get("index.js").unwrap().data),
);
let mut runtime = rustyscript::Runtime::with_tokio_runtime(
rustyscript::RuntimeOptions {
import_provider: Some(Box::new(cache)),
import_provider: Some(Box::new(import_provider::ImportProviderImpl)),
schema_whlist: HashSet::from(["trailbase".to_string()]),
..Default::default()
},
@@ -224,6 +224,7 @@ impl RuntimeSingleton {
// make it work. Thus, we're making the V8 VM a singleton (like Dart's).
static RUNTIME: LazyLock<RuntimeSingleton> = LazyLock::new(RuntimeSingleton::new);
#[derive(Clone)]
pub(crate) struct RuntimeHandle;
impl RuntimeHandle {
@@ -265,6 +266,10 @@ impl RuntimeHandle {
return Self {};
}
fn state(&self) -> &'static Vec<State> {
return &RUNTIME.state;
}
async fn apply<T>(
&self,
f: impl (FnOnce(&mut rustyscript::Runtime) -> T) + Send + Sync + 'static,
@@ -340,13 +345,17 @@ impl IntoResponse for JsResponseError {
}
}
/// Get's called from JS to `addRoute`.
fn route_callback(
/// Get's called from JS during `addRoute` and installs an axum HTTP handler.
///
/// The axum HTTP handler will then call back into the registered callback in JS.
fn add_route_to_router(
runtime_handle: RuntimeHandle,
router: Arc<Mutex<Option<Router<AppState>>>>,
method: String,
route: String,
) -> Result<(), AnyError> {
let method_uppercase = method.to_uppercase();
let route_path = route.clone();
let handler = move |params: RawPathParams, req: Request| async move {
let (parts, body) = req.into_parts();
@@ -356,12 +365,7 @@ fn route_callback(
"request deserialization failed".to_string(),
));
};
let Parts {
method: _,
uri,
headers,
..
} = parts;
let Parts { uri, headers, .. } = parts;
let path_params: Vec<(String, String)> = params
.iter()
@@ -386,7 +390,7 @@ fn route_callback(
body: Option<bytes::Bytes>,
}
let js_response = RuntimeHandle::new()
let js_response = runtime_handle
.apply(move |runtime| -> Result<JsResponse, rustyscript::Error> {
let tokio_runtime = runtime.tokio_runtime();
return tokio_runtime.block_on(async {
@@ -461,47 +465,68 @@ where
return from_value::<T>(arg.clone()).map_err(|err| Error::Runtime(err.to_string()));
}
pub(crate) async fn install_routes(module: Module) -> Result<Option<Router<AppState>>, AnyError> {
pub(crate) async fn install_routes(
runtime_handle: RuntimeHandle,
module: Module,
) -> Result<Option<Router<AppState>>, AnyError> {
use tokio::sync::oneshot;
let receivers: Vec<_> = RUNTIME
.state
let receivers: Vec<_> = runtime_handle
.state()
.iter()
.map(move |s| -> oneshot::Receiver<Option<Router<AppState>>> {
let module = module.clone();
let (sender, receiver) = oneshot::channel::<Option<Router<AppState>>>();
s.sender
.send(Message::Run(Box::new(move |runtime: &mut Runtime| {
let router = Arc::new(Mutex::new(Some(Router::<AppState>::new())));
.enumerate()
.map(
move |(index, state)| -> oneshot::Receiver<Option<Router<AppState>>> {
let (sender, receiver) = oneshot::channel::<Option<Router<AppState>>>();
// First install a native callback that builds an axum router.
let router_clone = router.clone();
runtime
.register_function("route", move |args: &[serde_json::Value]| {
let method: String = get_arg(args, 0)?;
let route: String = get_arg(args, 1)?;
let module = module.clone();
let runtime_handle = runtime_handle.clone();
route_callback(router_clone.clone(), method, route)
.map_err(|err| rustyscript::Error::Runtime(err.to_string()))?;
if let Err(err) = state
.sender
.send(Message::Run(Box::new(move |runtime: &mut Runtime| {
let router = Arc::new(Mutex::new(Some(Router::<AppState>::new())));
Ok(serde_json::Value::Null)
})
.unwrap();
// First install a native callback that builds an axum router.
let router_clone = router.clone();
runtime
.register_function("route", move |args: &[serde_json::Value]| {
let method: String = get_arg(args, 0)?;
let route: String = get_arg(args, 1)?;
// Then execute the script/module, i.e. statements in the file scope.
runtime.load_module(&module).unwrap();
add_route_to_router(runtime_handle.clone(), router_clone.clone(), method, route)
.map_err(|err| rustyscript::Error::Runtime(err.to_string()))?;
let router: Router<AppState> = router.lock().take().unwrap();
if router.has_routes() {
sender.send(Some(router)).unwrap();
} else {
sender.send(None).unwrap();
}
})))
.unwrap();
Ok(serde_json::Value::Null)
})
.expect("Failed to register 'route' function");
return receiver;
})
// Then execute the script/module, i.e. statements in the file scope.
//
// TODO: SWC is very spammy (at least in debug builds). Ideally, we'd lower the tracing
// filter level within this scope. Haven't found a good way, thus filtering it
// env-filter at the CLI level. We could try to use a dedicated reload layer:
// https://docs.rs/tracing-subscriber/latest/tracing_subscriber/reload/index.html
if let Err(err) = runtime.load_module(&module) {
panic!("Failed to load '{:?}': {err}", module.filename());
}
let router: Router<AppState> = router.lock().take().unwrap();
sender
.send(if router.has_routes() {
Some(router)
} else {
None
})
.expect("Failed to comm with parent");
})))
{
panic!("Failed to comm with v8 rt'{index}': {err}");
}
return receiver;
},
)
.collect();
let mut receivers = futures::future::join_all(receivers).await;
@@ -510,9 +535,35 @@ pub(crate) async fn install_routes(module: Module) -> Result<Option<Router<AppSt
return Ok(receivers.swap_remove(0)?);
}
#[derive(RustEmbed, Clone)]
#[folder = "js/dist/"]
struct JsRuntimeAssets;
pub(crate) async fn write_js_runtime_files(data_dir: &DataDir) {
if let Err(err) = tokio::fs::write(
data_dir.root().join("trailbase.js"),
cow_to_string(
JsRuntimeAssets::get("index.js")
.expect("Failed to read rt/index.js")
.data,
)
.as_str(),
)
.await
{
log::warn!("Failed to write 'trailbase.js': {err}");
}
if let Err(err) = tokio::fs::write(
data_dir.root().join("trailbase.d.ts"),
cow_to_string(
JsRuntimeAssets::get("index.d.ts")
.expect("Failed to read rt/index.d.ts")
.data,
)
.as_str(),
)
.await
{
log::warn!("Failed to write 'trailbase.d.ts': {err}");
}
}
#[cfg(test)]
mod tests {

View File

@@ -606,20 +606,15 @@ impl TryFrom<sqlite3_parser::ast::Stmt> for Table {
tbl_name,
args: _args,
..
} => {
#[cfg(debug_assertions)]
debug!("vTable args: {_args:?}");
Ok(Table {
name: tbl_name.name.to_string(),
strict: false,
columns: vec![],
foreign_keys: vec![],
unique: vec![],
virtual_table: true,
temporary: false,
})
}
} => Ok(Table {
name: tbl_name.name.to_string(),
strict: false,
columns: vec![],
foreign_keys: vec![],
unique: vec![],
virtual_table: true,
temporary: false,
}),
_ => Err(SchemaError::Precondition(
format!("expected 'CREATE TABLE', got: {value:?}").into(),
)),

View File

@@ -1,7 +1,6 @@
use libsql::Connection;
use log::*;
use std::path::PathBuf;
use std::rc::Rc;
use thiserror::Error;
use trailbase_sqlite::{connect_sqlite, query_one_row};
@@ -9,6 +8,7 @@ use crate::app_state::{AppState, AppStateArgs};
use crate::auth::jwt::{JwtHelper, JwtHelperError};
use crate::config::load_or_init_config_textproto;
use crate::constants::USER_TABLE;
use crate::js::write_js_runtime_files;
use crate::migrations::{apply_logs_migrations, apply_main_migrations};
use crate::rand::generate_random_string;
use crate::server::DataDir;
@@ -42,7 +42,6 @@ pub async fn init_app_state(
data_dir: DataDir,
public_dir: Option<PathBuf>,
dev: bool,
tokio_runtime: Rc<tokio::runtime::Runtime>,
) -> Result<(bool, AppState), InitError> {
// First create directory structure.
data_dir.ensure_directory_structure().await?;
@@ -101,6 +100,9 @@ pub async fn init_app_state(
debug!("Failed to load maxmind geoip DB '{geoip_db_path:?}': {err}");
}
// Write out the latest .js/.d.ts runtime files.
write_js_runtime_files(&data_dir).await;
let app_state = AppState::new(AppStateArgs {
data_dir: data_dir.clone(),
public_dir,
@@ -110,7 +112,6 @@ pub async fn init_app_state(
conn: main_conn.clone(),
logs_conn,
jwt,
tokio_runtime,
});
if new_db {

View File

@@ -94,13 +94,8 @@ impl Server {
where
O: std::future::Future<Output = Result<(), Box<dyn std::error::Error + Sync + Send>>>,
{
let (new_data_dir, state) = init::init_app_state(
opts.data_dir.clone(),
opts.public_dir.clone(),
opts.dev,
opts.tokio_runtime.clone(),
)
.await?;
let (new_data_dir, state) =
init::init_app_state(opts.data_dir.clone(), opts.public_dir.clone(), opts.dev).await?;
if new_data_dir {
on_first_init(state.clone())
@@ -113,7 +108,7 @@ impl Server {
let mut js_router = Some(Router::new());
for module in modules {
let fname = module.filename().to_owned();
let router = install_routes(module)
let router = install_routes(state.script_runtime(), module)
.await
.map_err(|err| InitError::ScriptError(err.to_string()))?;