@primate/core 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/private/App.d.ts +76 -149
- package/lib/private/App.js +22 -6
- package/lib/private/Flags.d.ts +5 -3
- package/lib/private/Flags.js +4 -2
- package/lib/private/app/Facade.d.ts +77 -156
- package/lib/private/app/Facade.js +4 -1
- package/lib/private/build/App.d.ts +4 -2
- package/lib/private/build/App.js +13 -4
- package/lib/private/build/client/index.js +24 -10
- package/lib/private/build/client/plugin/routes.d.ts +4 -0
- package/lib/private/build/client/plugin/routes.js +74 -0
- package/lib/private/build/hook.js +14 -6
- package/lib/private/build/index.d.ts +3 -2
- package/lib/private/build/index.js +9 -15
- package/lib/private/build/preclient/index.d.ts +3 -0
- package/lib/private/build/preclient/index.js +69 -0
- package/lib/private/build/preclient/plugin/routes.d.ts +4 -0
- package/lib/private/build/preclient/plugin/routes.js +44 -0
- package/lib/private/build/server/index.js +8 -5
- package/lib/private/build/server/plugin/assets.js +3 -3
- package/lib/private/build/server/plugin/native-addons.js +6 -8
- package/lib/private/build/server/plugin/node-imports.js +5 -7
- package/lib/private/build/server/plugin/route-client.d.ts +4 -0
- package/lib/private/build/server/plugin/route-client.js +110 -0
- package/lib/private/build/server/plugin/route.js +3 -10
- package/lib/private/build/server/plugin/virtual-pages.js +3 -3
- package/lib/private/build/server/plugin/virtual-routes.d.ts +2 -1
- package/lib/private/build/server/plugin/virtual-routes.js +27 -8
- package/lib/private/build/server/plugin/wasm.js +3 -2
- package/lib/private/client/Data.d.ts +1 -1
- package/lib/private/client/boot.js +2 -2
- package/lib/private/client/create-form.d.ts +11 -1
- package/lib/private/client/create-form.js +21 -3
- package/lib/private/client/index.d.ts +2 -2
- package/lib/private/client/navigate.d.ts +1 -0
- package/lib/private/client/navigate.js +7 -6
- package/lib/private/client/submit.d.ts +2 -1
- package/lib/private/client/submit.js +8 -7
- package/lib/private/client/{http.d.ts → transport.d.ts} +3 -3
- package/lib/private/client/{http.js → transport.js} +7 -9
- package/lib/private/config/schema.d.ts +5 -13
- package/lib/private/config/schema.js +1 -5
- package/lib/private/db/DB.d.ts +3 -1
- package/lib/private/db/MemoryDB.d.ts +5 -2
- package/lib/private/db/MemoryDB.js +33 -19
- package/lib/private/db/SQLDB.d.ts +23 -0
- package/lib/private/db/SQLDB.js +2 -0
- package/lib/private/db/errors.d.ts +74 -7
- package/lib/private/db/errors.js +31 -7
- package/lib/private/db/migrate/apply.js +8 -9
- package/lib/private/db/migrate/bundle.js +3 -2
- package/lib/private/db/migrate/create.js +18 -20
- package/lib/private/db/migrate/status.js +9 -10
- package/lib/private/db/migrate/store.d.ts +3 -3
- package/lib/private/db/migrate/store.js +5 -5
- package/lib/private/db/test.js +256 -115
- package/lib/private/db/testSQL.d.ts +50 -0
- package/lib/private/db/testSQL.js +196 -0
- package/lib/private/errors.d.ts +66 -9
- package/lib/private/errors.js +37 -16
- package/lib/private/frontend.d.ts +4 -4
- package/lib/private/frontend.js +11 -8
- package/lib/private/i18n/errors.d.ts +7 -1
- package/lib/private/i18n/errors.js +1 -1
- package/lib/private/i18n/index/types.d.ts +1 -1
- package/lib/private/i18n/locale.d.ts +1 -1
- package/lib/private/i18n/module.js +6 -5
- package/lib/private/index.d.ts +10 -2
- package/lib/private/index.js +13 -1
- package/lib/private/logger.d.ts +24 -0
- package/lib/private/logger.js +66 -0
- package/lib/private/module/Setup.d.ts +3 -0
- package/lib/private/module/create.d.ts +2 -1
- package/lib/private/module/create.js +4 -0
- package/lib/private/paths.d.ts +2 -3
- package/lib/private/paths.js +6 -12
- package/lib/private/request/ContentType.d.ts +3 -0
- package/lib/private/request/ContentType.js +2 -0
- package/lib/private/request/RequestBag.d.ts +2 -18
- package/lib/private/request/RequestBag.js +4 -16
- package/lib/private/request/RequestBody.d.ts +20 -28
- package/lib/private/request/RequestBody.js +63 -86
- package/lib/private/request/RequestFacade.d.ts +2 -2
- package/lib/private/request/handle.js +2 -2
- package/lib/private/request/parse.js +2 -4
- package/lib/private/request/route.js +15 -8
- package/lib/private/response/binary.d.ts +2 -2
- package/lib/private/response/binary.js +6 -6
- package/lib/private/response/error.js +6 -2
- package/lib/private/response/json.js +2 -2
- package/lib/private/response/null.d.ts +3 -0
- package/lib/private/response/null.js +6 -0
- package/lib/private/response/redirect.js +4 -3
- package/lib/private/response/respond.js +4 -4
- package/lib/private/response/sse.js +2 -2
- package/lib/private/response/text.js +2 -2
- package/lib/private/route/ContentTypeMap.d.ts +10 -0
- package/lib/private/route/ContentTypeMap.js +2 -0
- package/lib/private/route/Handler.d.ts +3 -2
- package/lib/private/route/NarrowedRequest.d.ts +23 -0
- package/lib/private/route/NarrowedRequest.js +2 -0
- package/lib/private/route/Options.d.ts +6 -1
- package/lib/private/route/Path.d.ts +2 -2
- package/lib/private/route/hook.d.ts +3 -1
- package/lib/private/route/hook.js +1 -2
- package/lib/private/route/router.d.ts +7 -11
- package/lib/private/route/router.js +13 -20
- package/lib/private/route.client.d.ts +36 -0
- package/lib/private/route.client.js +8 -0
- package/lib/private/route.d.ts +21 -5
- package/lib/private/route.js +21 -5
- package/lib/private/serve/App.d.ts +1 -2
- package/lib/private/serve/App.js +64 -58
- package/lib/private/serve/Init.d.ts +2 -0
- package/lib/private/serve/dev-module.js +2 -3
- package/lib/private/serve/index.js +5 -6
- package/lib/private/server/TAG.d.ts +1 -1
- package/lib/private/server/TAG.js +1 -1
- package/lib/private/session/index.d.ts +1 -1
- package/lib/private/session/index.js +3 -2
- package/lib/private/session/module.js +3 -3
- package/lib/private/session/schema.d.ts +3 -3
- package/lib/private/session/schema.js +4 -3
- package/lib/private/store/ExtractRelation.d.ts +7 -0
- package/lib/private/store/ExtractRelation.js +2 -0
- package/lib/private/store/ExtractSchema.d.ts +10 -0
- package/lib/private/{orm → store}/ForeignKey.d.ts +1 -1
- package/lib/private/store/Init.d.ts +13 -0
- package/lib/private/store/Init.js +2 -0
- package/lib/private/{orm/store.d.ts → store/Store.d.ts} +50 -50
- package/lib/private/{orm/store.js → store/Store.js} +163 -107
- package/lib/private/store/StoreInput.d.ts +11 -0
- package/lib/private/store/key.d.ts +8 -0
- package/lib/private/store/key.js +8 -0
- package/lib/private/{orm → store}/parse.d.ts +3 -3
- package/lib/private/{orm → store}/parse.js +7 -2
- package/lib/private/store/relation.d.ts +29 -0
- package/lib/private/store/relation.js +26 -0
- package/lib/private/store.d.ts +24 -0
- package/lib/private/store.js +10 -0
- package/lib/public/db/errors.d.ts +1 -1
- package/lib/public/db/errors.js +1 -1
- package/lib/public/db/testSQL.d.ts +2 -0
- package/lib/public/db/testSQL.js +2 -0
- package/lib/public/db.d.ts +1 -0
- package/lib/public/index.d.ts +1 -0
- package/lib/public/index.js +1 -1
- package/lib/public/response.d.ts +6 -6
- package/lib/public/response.js +4 -1
- package/lib/public/route.client.d.ts +2 -0
- package/lib/public/route.client.js +2 -0
- package/lib/public/store.d.ts +2 -0
- package/lib/public/store.js +2 -0
- package/package.json +24 -17
- package/lib/private/bye.d.ts +0 -3
- package/lib/private/bye.js +0 -4
- package/lib/private/log.d.ts +0 -20
- package/lib/private/log.js +0 -47
- package/lib/private/orm/ExtractSchema.d.ts +0 -9
- package/lib/private/orm/StoreInput.d.ts +0 -10
- package/lib/private/orm/key.d.ts +0 -8
- package/lib/private/orm/key.js +0 -8
- package/lib/private/orm/relation.d.ts +0 -43
- package/lib/private/orm/relation.js +0 -26
- package/lib/private/request/Verb.d.ts +0 -4
- package/lib/private/request/Verb.js +0 -2
- package/lib/private/request/verbs.d.ts +0 -3
- package/lib/private/request/verbs.js +0 -12
- package/lib/private/route/wrap.d.ts +0 -2
- package/lib/private/route/wrap.js +0 -12
- package/lib/public/log.d.ts +0 -2
- package/lib/public/log.js +0 -2
- package/lib/public/orm/key.d.ts +0 -2
- package/lib/public/orm/key.js +0 -2
- package/lib/public/orm/relation.d.ts +0 -2
- package/lib/public/orm/relation.js +0 -2
- package/lib/public/orm/store.d.ts +0 -2
- package/lib/public/orm/store.js +0 -2
- package/lib/public/request/verbs.d.ts +0 -2
- package/lib/public/request/verbs.js +0 -2
- /package/lib/private/{orm → store}/ExtractSchema.js +0 -0
- /package/lib/private/{orm → store}/ForeignKey.js +0 -0
- /package/lib/private/{orm → store}/PrimaryKey.d.ts +0 -0
- /package/lib/private/{orm → store}/PrimaryKey.js +0 -0
- /package/lib/private/{orm → store}/StoreInput.js +0 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import E from "#errors";
|
|
2
|
-
import wrap from "#route/wrap";
|
|
3
2
|
import fs from "@rcompat/fs";
|
|
4
|
-
const
|
|
3
|
+
const empty = "export default {};";
|
|
5
4
|
export default function plugin_server_route(app) {
|
|
6
5
|
const path_routes = app.path.routes;
|
|
7
6
|
return {
|
|
@@ -28,23 +27,17 @@ export default function plugin_server_route(app) {
|
|
|
28
27
|
if (!file || !extension) {
|
|
29
28
|
throw E.build_missing_route(relative, path_routes);
|
|
30
29
|
}
|
|
31
|
-
// normalise "routes/foo.ext" -> "foo" for router
|
|
32
|
-
const relative_from_routes = file.path.split("routes").pop();
|
|
33
|
-
const no_extensions = relative_from_routes
|
|
34
|
-
.replace(/^[\\/]/, "")
|
|
35
|
-
.slice(0, -extension.length);
|
|
36
|
-
const route_path = no_extensions.replace(/\\/g, "/");
|
|
37
30
|
const resolveDir = file.directory.path;
|
|
38
31
|
const watchFiles = [file.path];
|
|
39
32
|
const binder = app.binder(file);
|
|
40
33
|
if (!binder)
|
|
41
|
-
return { contents, loader: "js", resolveDir, watchFiles };
|
|
34
|
+
return { contents: empty, loader: "js", resolveDir, watchFiles };
|
|
42
35
|
const compiled = await binder(file, {
|
|
43
36
|
build: { id: app.id },
|
|
44
37
|
context: "routes",
|
|
45
38
|
});
|
|
46
39
|
return {
|
|
47
|
-
contents:
|
|
40
|
+
contents: compiled,
|
|
48
41
|
loader: extension === ".ts" ? "ts" : "js",
|
|
49
42
|
resolveDir,
|
|
50
43
|
watchFiles,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
const
|
|
1
|
+
import runtime from "@rcompat/runtime";
|
|
2
|
+
const core = await runtime.projectRoot(import.meta.dirname);
|
|
3
3
|
export default function plugin_server_virtual_pages(app) {
|
|
4
4
|
return {
|
|
5
5
|
name: "primate/server/virtual/pages",
|
|
@@ -9,7 +9,7 @@ export default function plugin_server_virtual_pages(app) {
|
|
|
9
9
|
});
|
|
10
10
|
build.onLoad({ filter: /.*/, namespace: "primate-pages" }, async () => {
|
|
11
11
|
const filter = /^.*\.html$/ui;
|
|
12
|
-
const defaults =
|
|
12
|
+
const defaults = core.join("lib", "private", "defaults");
|
|
13
13
|
const pages = {};
|
|
14
14
|
for (const file of await defaults.files({ filter })) {
|
|
15
15
|
pages[file.name] = file;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type BuildApp from "#build/App";
|
|
2
2
|
import type { Plugin } from "esbuild";
|
|
3
|
-
|
|
3
|
+
declare function plugin_server_virtual_routes(app: BuildApp): Plugin;
|
|
4
|
+
export default plugin_server_virtual_routes;
|
|
4
5
|
//# sourceMappingURL=virtual-routes.d.ts.map
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
function is_hook_file(p) {
|
|
2
|
+
const basename = p.split("/").at(-1) ?? p;
|
|
3
|
+
return basename === "+hook" || basename.startsWith("+hook.");
|
|
4
|
+
}
|
|
5
|
+
function plugin_server_virtual_routes(app) {
|
|
2
6
|
const extension_pattern = new RegExp(`(${app.extensions.map(e => e.replace(".", "\\.")).join("|")})$`);
|
|
3
|
-
|
|
4
|
-
!f.name.
|
|
5
|
-
|
|
7
|
+
function is_route_file(f) {
|
|
8
|
+
return !f.name.endsWith("~") &&
|
|
9
|
+
!f.name.startsWith(".") &&
|
|
10
|
+
!f.name.startsWith("-") &&
|
|
11
|
+
extension_pattern.test(f.path);
|
|
12
|
+
}
|
|
6
13
|
return {
|
|
7
14
|
name: "primate/server/virtual/routes",
|
|
8
15
|
setup(build) {
|
|
@@ -16,13 +23,24 @@ export default function plugin_server_virtual_routes(app) {
|
|
|
16
23
|
recursive: true,
|
|
17
24
|
});
|
|
18
25
|
const contents = `
|
|
19
|
-
|
|
26
|
+
import router from "primate/router";
|
|
27
|
+
${route_files.map((file, i) => {
|
|
28
|
+
const path = app.basename(file, app.path.routes);
|
|
29
|
+
return `import route${i} from "app:route/${path}";`;
|
|
30
|
+
}).join("\n")}
|
|
31
|
+
const routes = [];
|
|
20
32
|
${route_files.map((file, i) => {
|
|
21
33
|
const path = app.basename(file, app.path.routes);
|
|
22
|
-
return
|
|
23
|
-
|
|
34
|
+
return is_hook_file(path)
|
|
35
|
+
? `router.addHook("${path}", route${i});
|
|
36
|
+
routes.push(["${path}", route${i}]);
|
|
37
|
+
`
|
|
38
|
+
: `for (const [method, { handler, options }] of Object.entries(route${i})) {
|
|
39
|
+
router.add("${path}", method, handler, options);
|
|
40
|
+
}
|
|
41
|
+
routes.push(["${path}", route${i}]);`;
|
|
24
42
|
}).join("\n")}
|
|
25
|
-
export default
|
|
43
|
+
export default routes;
|
|
26
44
|
`;
|
|
27
45
|
const watchDirs = (await app.path.routes.dirs({
|
|
28
46
|
recursive: true,
|
|
@@ -38,4 +56,5 @@ export default function plugin_server_virtual_routes(app) {
|
|
|
38
56
|
},
|
|
39
57
|
};
|
|
40
58
|
}
|
|
59
|
+
export default plugin_server_virtual_routes;
|
|
41
60
|
//# sourceMappingURL=virtual-routes.js.map
|
|
@@ -20,13 +20,14 @@ export default function plugin_server_wasm(app) {
|
|
|
20
20
|
});
|
|
21
21
|
build.onLoad({ filter: /.*/, namespace: "wasm-dev" }, async (args) => {
|
|
22
22
|
const wasm_file = app.runpath("wasm", args.path + ".wasm");
|
|
23
|
+
const fs_path = import.meta.resolve("@rcompat/fs");
|
|
23
24
|
return {
|
|
24
25
|
contents: `
|
|
25
|
-
|
|
26
|
+
const { default: fs } = await import("${fs_path}");
|
|
26
27
|
export default await fs.ref("${wasm_file.path}").bytes();
|
|
27
28
|
`,
|
|
28
29
|
loader: "js",
|
|
29
|
-
resolveDir:
|
|
30
|
+
resolveDir: new URL(".", import.meta.url).pathname,
|
|
30
31
|
};
|
|
31
32
|
});
|
|
32
33
|
},
|
|
@@ -2,7 +2,7 @@ import navigate from "#client/navigate";
|
|
|
2
2
|
import root from "#client/root";
|
|
3
3
|
import storage from "#client/storage";
|
|
4
4
|
import submit from "#client/submit";
|
|
5
|
-
import
|
|
5
|
+
import http from "@rcompat/http";
|
|
6
6
|
export default (u) => {
|
|
7
7
|
root.set(u);
|
|
8
8
|
const { location, history } = globalThis;
|
|
@@ -50,7 +50,7 @@ export default (u) => {
|
|
|
50
50
|
const action = target.action ?? location.pathname;
|
|
51
51
|
const url = new URL(action);
|
|
52
52
|
const data = new FormData(target);
|
|
53
|
-
const form = enctype === MIME.MULTIPART_FORM_DATA
|
|
53
|
+
const form = enctype === http.MIME.MULTIPART_FORM_DATA
|
|
54
54
|
? data
|
|
55
55
|
: new URLSearchParams(data);
|
|
56
56
|
try {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Dict } from "@rcompat/type";
|
|
2
2
|
type FormId = string;
|
|
3
|
-
type FieldErrors =
|
|
3
|
+
type FieldErrors = string[];
|
|
4
4
|
type FormErrors = {
|
|
5
5
|
form: FieldErrors;
|
|
6
6
|
fields: Dict<FieldErrors>;
|
|
@@ -15,11 +15,21 @@ export type FormView = FormSnapshot & {
|
|
|
15
15
|
submit: (event?: Event) => Promise<void>;
|
|
16
16
|
};
|
|
17
17
|
type FormSubscriber = (snapshot: FormSnapshot) => void;
|
|
18
|
+
export type MethodMeta = {
|
|
19
|
+
contentType?: string;
|
|
20
|
+
};
|
|
21
|
+
export type ClientMethod<Values extends Dict = Dict> = MethodMeta & ((args: {
|
|
22
|
+
body: Values;
|
|
23
|
+
}) => Promise<Response>);
|
|
18
24
|
export type FormInit = {
|
|
19
25
|
id?: string;
|
|
20
26
|
method?: "POST" | "PUT" | "PATCH" | "DELETE";
|
|
21
27
|
url?: string;
|
|
22
28
|
headers?: Dict<string>;
|
|
29
|
+
action?: (args: {
|
|
30
|
+
body: unknown;
|
|
31
|
+
}) => Promise<Response>;
|
|
32
|
+
contentType?: string;
|
|
23
33
|
};
|
|
24
34
|
type FormController = {
|
|
25
35
|
subscribe(fn: FormSubscriber): () => void;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import extract_issues from "#client/extract-issues";
|
|
2
2
|
import submit from "#client/submit";
|
|
3
|
+
import is from "@rcompat/is";
|
|
3
4
|
function decode_pointer_segment(segment) {
|
|
4
5
|
// decode JSON Pointer: ~1 -> /, ~0 -> ~
|
|
5
6
|
return segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
@@ -21,6 +22,21 @@ function pointer_to_fieldname(path) {
|
|
|
21
22
|
}
|
|
22
23
|
return name;
|
|
23
24
|
}
|
|
25
|
+
function content_type_body(contentType, form_data) {
|
|
26
|
+
if (contentType === "application/json")
|
|
27
|
+
return Object.fromEntries(form_data);
|
|
28
|
+
if (contentType === "multipart/form-data")
|
|
29
|
+
return form_data;
|
|
30
|
+
// default: application/x-www-form-urlencoded
|
|
31
|
+
return new URLSearchParams(form_data);
|
|
32
|
+
}
|
|
33
|
+
function form_data_body(form_data) {
|
|
34
|
+
for (const value of form_data.values()) {
|
|
35
|
+
if (value instanceof File && value.size > 0)
|
|
36
|
+
return form_data;
|
|
37
|
+
}
|
|
38
|
+
return new URLSearchParams(form_data);
|
|
39
|
+
}
|
|
24
40
|
export default function createForm(init) {
|
|
25
41
|
const id = init.id ?? `form-${crypto.randomUUID()}`;
|
|
26
42
|
let snapshot = {
|
|
@@ -83,7 +99,9 @@ export default function createForm(init) {
|
|
|
83
99
|
const form_data = new FormData(form_element);
|
|
84
100
|
setSubmitting(true);
|
|
85
101
|
try {
|
|
86
|
-
const response = await
|
|
102
|
+
const response = await (is.defined(init.action)
|
|
103
|
+
? init.action({ body: content_type_body(init.contentType, form_data) })
|
|
104
|
+
: submit(url, form_data_body(form_data), method));
|
|
87
105
|
if (response.ok) {
|
|
88
106
|
// on success: clear errors, let the app decide what to do next
|
|
89
107
|
// (redirect/reload)
|
|
@@ -91,8 +109,8 @@ export default function createForm(init) {
|
|
|
91
109
|
publish();
|
|
92
110
|
return;
|
|
93
111
|
}
|
|
94
|
-
|
|
95
|
-
const issues = extract_issues(
|
|
112
|
+
// all issues, all paths
|
|
113
|
+
const issues = extract_issues(await response.json());
|
|
96
114
|
setErrors(issues);
|
|
97
115
|
}
|
|
98
116
|
catch (error) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FormInit, FormView } from "#client/create-form";
|
|
1
|
+
import type { ClientMethod, FormInit, FormView, MethodMeta } from "#client/create-form";
|
|
2
2
|
import createForm from "#client/create-form";
|
|
3
3
|
import type Data from "#client/Data";
|
|
4
4
|
import type Publish from "#client/Publish";
|
|
@@ -19,5 +19,5 @@ declare const client: {
|
|
|
19
19
|
toValidated: typeof toValidated;
|
|
20
20
|
};
|
|
21
21
|
export default client;
|
|
22
|
-
export type { Data, FormInit, FormView, Publish, Render, ValidateInit, ValidateUpdater, ValidationError, ViewResponse
|
|
22
|
+
export type { ClientMethod, Data, FormInit, FormView, MethodMeta, Publish, Render, ValidateInit, ValidateUpdater, ValidationError, ViewResponse };
|
|
23
23
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import http from "#client/http";
|
|
2
1
|
import root from "#client/root";
|
|
3
2
|
import storage from "#client/storage";
|
|
4
|
-
import
|
|
3
|
+
import transport from "#client/transport";
|
|
4
|
+
import http from "@rcompat/http";
|
|
5
5
|
const headers = {
|
|
6
|
-
Accept: MIME.APPLICATION_JSON,
|
|
6
|
+
Accept: http.MIME.APPLICATION_JSON,
|
|
7
7
|
};
|
|
8
8
|
const get_by_id_or_name = (name) => document.getElementById(name) ?? document.getElementsByName(name)[0];
|
|
9
9
|
const scroll_hash = (hash) => {
|
|
@@ -18,13 +18,14 @@ async function goto(target, state = false, after) {
|
|
|
18
18
|
try {
|
|
19
19
|
const { scrollTop } = globalThis.document.scrollingElement;
|
|
20
20
|
const { location } = globalThis;
|
|
21
|
-
const
|
|
22
|
-
|
|
21
|
+
const path = target.pathname + target.search;
|
|
22
|
+
const { requested, response } = await transport.refetch(path, { headers });
|
|
23
|
+
if (transport.is_json(response)) {
|
|
23
24
|
if (response.ok)
|
|
24
25
|
root.update(await response.json());
|
|
25
26
|
if (state) {
|
|
26
27
|
storage.new({ hash: location.hash, pathname: location.pathname, scrollTop });
|
|
27
|
-
history.pushState({}, "", `${
|
|
28
|
+
history.pushState({}, "", `${path}${target.hash}`);
|
|
28
29
|
}
|
|
29
30
|
after?.();
|
|
30
31
|
return;
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import http from "#client/http";
|
|
2
1
|
import root from "#client/root";
|
|
3
2
|
import storage from "#client/storage";
|
|
4
|
-
import
|
|
3
|
+
import transport from "#client/transport";
|
|
4
|
+
import http from "@rcompat/http";
|
|
5
5
|
const headers = {
|
|
6
|
-
Accept: MIME.APPLICATION_JSON,
|
|
6
|
+
Accept: http.MIME.APPLICATION_JSON,
|
|
7
7
|
};
|
|
8
|
-
|
|
9
|
-
const { requested, response } = await
|
|
8
|
+
async function submit(pathname, body, method) {
|
|
9
|
+
const { requested, response } = await transport.refetch(pathname, {
|
|
10
10
|
body, headers, method,
|
|
11
11
|
});
|
|
12
12
|
if (response.redirected) {
|
|
13
13
|
const { location, document, history } = globalThis;
|
|
14
14
|
const scrollTop = document.scrollingElement?.scrollTop ?? 0;
|
|
15
|
-
if (
|
|
15
|
+
if (transport.is_json(response))
|
|
16
16
|
root.update(await response.json());
|
|
17
17
|
storage.new({
|
|
18
18
|
hash: location.hash,
|
|
@@ -23,7 +23,7 @@ export default async function submit(pathname, body, method) {
|
|
|
23
23
|
history.pushState({}, "", url.pathname + url.search);
|
|
24
24
|
return response;
|
|
25
25
|
}
|
|
26
|
-
if (
|
|
26
|
+
if (transport.is_json(response)) {
|
|
27
27
|
if (response.ok) {
|
|
28
28
|
root.update(await response.json());
|
|
29
29
|
history.replaceState({}, "", requested.pathname + requested.search);
|
|
@@ -38,4 +38,5 @@ export default async function submit(pathname, body, method) {
|
|
|
38
38
|
globalThis.location.assign(target);
|
|
39
39
|
return response;
|
|
40
40
|
}
|
|
41
|
+
export default submit;
|
|
41
42
|
//# sourceMappingURL=submit.js.map
|
|
@@ -4,10 +4,10 @@ declare function refetch(input: string | URL, init?: RequestInit, max_hops?: num
|
|
|
4
4
|
}>;
|
|
5
5
|
declare function is_json(response: Response): boolean;
|
|
6
6
|
declare function submit(pathname: string, body: any, method: string): Promise<Response>;
|
|
7
|
-
declare const
|
|
7
|
+
declare const transport: {
|
|
8
8
|
refetch: typeof refetch;
|
|
9
9
|
is_json: typeof is_json;
|
|
10
10
|
submit: typeof submit;
|
|
11
11
|
};
|
|
12
|
-
export default
|
|
13
|
-
//# sourceMappingURL=
|
|
12
|
+
export default transport;
|
|
13
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import http from "@rcompat/http";
|
|
2
2
|
const sameorigin = (url) => url.origin === globalThis.location.origin;
|
|
3
3
|
const get_location = (response, base) => {
|
|
4
4
|
if (response.type === "opaqueredirect")
|
|
@@ -7,6 +7,8 @@ const get_location = (response, base) => {
|
|
|
7
7
|
return location !== null ? new URL(location, base) : null;
|
|
8
8
|
};
|
|
9
9
|
async function refetch(input, init = {}, max_hops = 5) {
|
|
10
|
+
console.log("location.href", globalThis.location.href);
|
|
11
|
+
console.log("input", input.toString());
|
|
10
12
|
let url = new URL(input.toString(), globalThis.location.href);
|
|
11
13
|
const method = (init.method ?? "GET").toUpperCase();
|
|
12
14
|
let hops = 0;
|
|
@@ -36,7 +38,7 @@ async function refetch(input, init = {}, max_hops = 5) {
|
|
|
36
38
|
}
|
|
37
39
|
function is_json(response) {
|
|
38
40
|
const raw = response.headers.get("content-type") ?? "";
|
|
39
|
-
return raw.split(";")[0].trim() === MIME.APPLICATION_JSON;
|
|
41
|
+
return raw.split(";")[0].trim() === http.MIME.APPLICATION_JSON;
|
|
40
42
|
}
|
|
41
43
|
async function submit(pathname, body, method) {
|
|
42
44
|
const { requested, response } = await refetch(pathname, { body, method });
|
|
@@ -48,10 +50,6 @@ async function submit(pathname, body, method) {
|
|
|
48
50
|
}
|
|
49
51
|
return response;
|
|
50
52
|
}
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
submit,
|
|
55
|
-
};
|
|
56
|
-
export default http;
|
|
57
|
-
//# sourceMappingURL=http.js.map
|
|
53
|
+
const transport = { refetch, is_json, submit };
|
|
54
|
+
export default transport;
|
|
55
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -3,6 +3,7 @@ import type Module from "#Module";
|
|
|
3
3
|
import type { Dict } from "@rcompat/type";
|
|
4
4
|
import type { ObjectType, Parsed } from "pema";
|
|
5
5
|
declare const _default: ObjectType<import("pema").NormalizeSchemaObject<{
|
|
6
|
+
readonly conditions: import("pema").DefaultType<import("pema").ArrayType<import("pema").StringType>, string[]>;
|
|
6
7
|
readonly http: {
|
|
7
8
|
readonly csp: import("pema").OptionalType<import("pema").RecordType<import("pema").StringType, import("pema").ArrayType<import("pema").StringType>>>;
|
|
8
9
|
readonly headers: import("pema").OptionalType<import("pema").RecordType<import("pema").StringType, import("pema").StringType>>;
|
|
@@ -24,10 +25,10 @@ declare const _default: ObjectType<import("pema").NormalizeSchemaObject<{
|
|
|
24
25
|
readonly db: {
|
|
25
26
|
readonly migrations: import("pema").OptionalType<ObjectType<{
|
|
26
27
|
table: import("pema").StringType;
|
|
27
|
-
db: import("pema").PureType<DB
|
|
28
|
+
db: import("pema").PureType<DB<unknown>, "PureType">;
|
|
28
29
|
}, {
|
|
29
30
|
table: string;
|
|
30
|
-
db: DB
|
|
31
|
+
db: DB<unknown>;
|
|
31
32
|
}>>;
|
|
32
33
|
};
|
|
33
34
|
readonly env: {
|
|
@@ -39,12 +40,8 @@ declare const _default: ObjectType<import("pema").NormalizeSchemaObject<{
|
|
|
39
40
|
name: import("pema").StringType;
|
|
40
41
|
setup: import("pema").FunctionType;
|
|
41
42
|
}, Module>>, Module[]>;
|
|
42
|
-
readonly request: {
|
|
43
|
-
readonly body: {
|
|
44
|
-
readonly parse: import("pema").DefaultType<import("pema").BooleanType, true>;
|
|
45
|
-
};
|
|
46
|
-
};
|
|
47
43
|
}>, {
|
|
44
|
+
conditions: string[];
|
|
48
45
|
http: {
|
|
49
46
|
csp: Record<string, string[]> | undefined;
|
|
50
47
|
headers: Record<string, string> | undefined;
|
|
@@ -66,7 +63,7 @@ declare const _default: ObjectType<import("pema").NormalizeSchemaObject<{
|
|
|
66
63
|
db: {
|
|
67
64
|
migrations: {
|
|
68
65
|
table: string;
|
|
69
|
-
db: DB
|
|
66
|
+
db: DB<unknown>;
|
|
70
67
|
} | undefined;
|
|
71
68
|
};
|
|
72
69
|
env: {
|
|
@@ -75,11 +72,6 @@ declare const _default: ObjectType<import("pema").NormalizeSchemaObject<{
|
|
|
75
72
|
}> | undefined;
|
|
76
73
|
};
|
|
77
74
|
modules: Module[];
|
|
78
|
-
request: {
|
|
79
|
-
body: {
|
|
80
|
-
parse: boolean;
|
|
81
|
-
};
|
|
82
|
-
};
|
|
83
75
|
}>;
|
|
84
76
|
export default _default;
|
|
85
77
|
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "@rcompat/fs";
|
|
2
2
|
import p from "pema";
|
|
3
3
|
export default p({
|
|
4
|
+
conditions: p.array(p.string).unique().default([]),
|
|
4
5
|
http: {
|
|
5
6
|
csp: p.dict(p.array(p.string)).optional(),
|
|
6
7
|
headers: p.dict().optional(),
|
|
@@ -34,10 +35,5 @@ export default p({
|
|
|
34
35
|
}).shape())
|
|
35
36
|
.uniqueBy(member => member.name)
|
|
36
37
|
.default([]),
|
|
37
|
-
request: {
|
|
38
|
-
body: {
|
|
39
|
-
parse: p.boolean.default(true),
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
38
|
});
|
|
43
39
|
//# sourceMappingURL=schema.js.map
|
package/lib/private/db/DB.d.ts
CHANGED
|
@@ -21,9 +21,10 @@ export type Schema = {
|
|
|
21
21
|
introspect(name: string, pk?: PK): MaybePromise<MaybeTable>;
|
|
22
22
|
alter(name: string, diff: SchemaDiff): MaybePromise<void>;
|
|
23
23
|
};
|
|
24
|
-
export default interface DB {
|
|
24
|
+
export default interface DB<Client = unknown> {
|
|
25
25
|
schema: Schema;
|
|
26
26
|
close(): MaybePromise<void>;
|
|
27
|
+
readonly client: Client;
|
|
27
28
|
create<O extends Dict>(as: As, record: Dict): MaybePromise<O>;
|
|
28
29
|
read(as: As, args: {
|
|
29
30
|
count: true;
|
|
@@ -34,6 +35,7 @@ export default interface DB {
|
|
|
34
35
|
where: Dict;
|
|
35
36
|
fields?: string[];
|
|
36
37
|
limit?: number;
|
|
38
|
+
offset?: number;
|
|
37
39
|
sort?: Sort;
|
|
38
40
|
with?: With;
|
|
39
41
|
}): MaybePromise<Dict[]>;
|
|
@@ -4,9 +4,11 @@ import type { Schema } from "#db/DB";
|
|
|
4
4
|
import type DataDict from "#db/DataDict";
|
|
5
5
|
import type Sort from "#db/Sort";
|
|
6
6
|
import type With from "#db/With";
|
|
7
|
-
import type { Dict, MaybePromise } from "@rcompat/type";
|
|
8
|
-
|
|
7
|
+
import type { Dict, MaybePromise, PartialDict } from "@rcompat/type";
|
|
8
|
+
type Tables = PartialDict<Dict[]>;
|
|
9
|
+
export default class MemoryDB implements DB<Tables> {
|
|
9
10
|
#private;
|
|
11
|
+
get client(): Tables;
|
|
10
12
|
get schema(): Schema;
|
|
11
13
|
close(): void;
|
|
12
14
|
create<O extends Dict>(as: As, record: Dict): MaybePromise<O>;
|
|
@@ -30,4 +32,5 @@ export default class MemoryDB implements DB {
|
|
|
30
32
|
where: DataDict;
|
|
31
33
|
}): number;
|
|
32
34
|
}
|
|
35
|
+
export {};
|
|
33
36
|
//# sourceMappingURL=MemoryDB.d.ts.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import E from "#db/errors";
|
|
2
2
|
import assert from "@rcompat/assert";
|
|
3
|
-
import
|
|
3
|
+
import dict from "@rcompat/dict";
|
|
4
4
|
import is from "@rcompat/is";
|
|
5
5
|
function escape_re(s) {
|
|
6
6
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -83,6 +83,22 @@ function match(record, where) {
|
|
|
83
83
|
return false;
|
|
84
84
|
break;
|
|
85
85
|
}
|
|
86
|
+
case "$in": {
|
|
87
|
+
const arr = op_val;
|
|
88
|
+
if (is.date(value)) {
|
|
89
|
+
if (!arr.some(v => is.date(v) && v.getTime() === value.getTime()))
|
|
90
|
+
return false;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
if (is.url(value)) {
|
|
94
|
+
if (!arr.some(v => is.url(v) && v.href === value.href))
|
|
95
|
+
return false;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
if (!arr.includes(value))
|
|
99
|
+
return false;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
86
102
|
default:
|
|
87
103
|
throw E.operator_unknown(k, op);
|
|
88
104
|
}
|
|
@@ -101,24 +117,20 @@ function filter(record, fields) {
|
|
|
101
117
|
return Object.fromEntries(Object.entries(record)
|
|
102
118
|
.filter(([key]) => fields.includes(key)));
|
|
103
119
|
}
|
|
104
|
-
function
|
|
105
|
-
return
|
|
120
|
+
function to_sorted(d1, d2, sort) {
|
|
121
|
+
return (dict.entries(sort)
|
|
122
|
+
.map(([k, value]) => [k, value === "asc" ? 1 : -1])
|
|
106
123
|
.reduce((sorting, [field, direction]) => {
|
|
107
124
|
const left = d1[field];
|
|
108
125
|
const right = d2[field];
|
|
109
|
-
|
|
110
|
-
if (sorting !== 0) {
|
|
126
|
+
if (sorting !== 0)
|
|
111
127
|
return sorting;
|
|
112
|
-
|
|
113
|
-
// equal, sorting doesn't change
|
|
114
|
-
if (left === right) {
|
|
128
|
+
if (left === right)
|
|
115
129
|
return sorting;
|
|
116
|
-
|
|
117
|
-
if (left < right) {
|
|
130
|
+
if (left < right)
|
|
118
131
|
return -1 * direction;
|
|
119
|
-
}
|
|
120
132
|
return direction;
|
|
121
|
-
}, 0);
|
|
133
|
+
}, 0));
|
|
122
134
|
}
|
|
123
135
|
export default class MemoryDB {
|
|
124
136
|
#tables = {};
|
|
@@ -127,6 +139,9 @@ export default class MemoryDB {
|
|
|
127
139
|
this.#tables[name] ??= [];
|
|
128
140
|
return this.#tables[name];
|
|
129
141
|
}
|
|
142
|
+
get client() {
|
|
143
|
+
return this.#tables;
|
|
144
|
+
}
|
|
130
145
|
get schema() {
|
|
131
146
|
return {
|
|
132
147
|
create: async (table, _pk, types) => {
|
|
@@ -206,9 +221,10 @@ export default class MemoryDB {
|
|
|
206
221
|
const sort = args.sort ?? {};
|
|
207
222
|
const sorted = Object.keys(sort).length === 0
|
|
208
223
|
? matches
|
|
209
|
-
: matches.toSorted((a, b) =>
|
|
224
|
+
: matches.toSorted((a, b) => to_sorted(a, b, sort));
|
|
225
|
+
const offset = args.offset ?? 0;
|
|
210
226
|
const limit = args.limit ?? sorted.length;
|
|
211
|
-
const base_rows = sorted.slice(
|
|
227
|
+
const base_rows = sorted.slice(offset, offset + limit);
|
|
212
228
|
// no relations -> preserve existing behavior
|
|
213
229
|
if (args.with === undefined) {
|
|
214
230
|
const fields = args.fields ?? [];
|
|
@@ -258,7 +274,7 @@ export default class MemoryDB {
|
|
|
258
274
|
// filter by target_pk equality + optional where
|
|
259
275
|
let candidates = target.filter(t => t[target_pk] === fk_value && match(t, r_where));
|
|
260
276
|
if (r_sort !== undefined && Object.keys(r_sort).length > 0) {
|
|
261
|
-
candidates = candidates.toSorted((a, b) =>
|
|
277
|
+
candidates = candidates.toSorted((a, b) => to_sorted(a, b, r_sort));
|
|
262
278
|
}
|
|
263
279
|
if (r_limit !== undefined)
|
|
264
280
|
candidates = candidates.slice(0, r_limit);
|
|
@@ -290,7 +306,7 @@ export default class MemoryDB {
|
|
|
290
306
|
const key = parent_full[parent_pk];
|
|
291
307
|
let rows = grouped.get(key) ?? [];
|
|
292
308
|
if (r_sort !== undefined && Object.keys(r_sort).length > 0) {
|
|
293
|
-
rows = rows.toSorted((a, b) =>
|
|
309
|
+
rows = rows.toSorted((a, b) => to_sorted(a, b, r_sort));
|
|
294
310
|
}
|
|
295
311
|
if (kind === "one") {
|
|
296
312
|
if (r_limit !== undefined)
|
|
@@ -314,9 +330,7 @@ export default class MemoryDB {
|
|
|
314
330
|
const matched = table.filter(record => match(record, args.where));
|
|
315
331
|
const pk = as.pk;
|
|
316
332
|
return matched.map(record => {
|
|
317
|
-
const changed =
|
|
318
|
-
.filter(([, value]) => value !== null)
|
|
319
|
-
.get();
|
|
333
|
+
const changed = dict.filter({ ...record, ...args.set }, (_, value) => !is.null(value));
|
|
320
334
|
const index = pk !== null
|
|
321
335
|
? table.findIndex(stored => stored[pk] === record[pk])
|
|
322
336
|
: table.findIndex(stored => stored === record);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type DB from "#db/DB";
|
|
2
|
+
import type { ArrayType, ObjectType, OptionalType, Parsed } from "pema";
|
|
3
|
+
export type SQLInput = ObjectType<any>;
|
|
4
|
+
export type SQLOutput = ArrayType<any>;
|
|
5
|
+
export type WordChar = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "_";
|
|
6
|
+
export type ReadWord<S extends string, Acc extends string = ""> = S extends `${infer C extends WordChar}${infer Rest}` ? ReadWord<Rest, `${Acc}${C}`> : Acc;
|
|
7
|
+
export type ExtractPlaceholders<S extends string> = S extends `${string}:${infer After}` ? ReadWord<After> | ExtractPlaceholders<After> : never;
|
|
8
|
+
type RequiredKeys<I extends ObjectType<any>> = {
|
|
9
|
+
[K in keyof I["properties"]]: I["properties"][K] extends OptionalType<any> ? never : K;
|
|
10
|
+
}[keyof I["properties"]];
|
|
11
|
+
type MissingPlaceholders<I extends ObjectType<any>, Q extends string> = Exclude<RequiredKeys<I>, ExtractPlaceholders<Q>>;
|
|
12
|
+
export type CheckPlaceholders<I extends ObjectType<any>, Q extends string> = MissingPlaceholders<I, Q> extends never ? I : `Missing placeholders in query: ${MissingPlaceholders<I, Q> & string}`;
|
|
13
|
+
export default interface SQLDB<Client = unknown> extends DB<Client> {
|
|
14
|
+
sql<Q extends string, I extends ObjectType<{
|
|
15
|
+
[K in ExtractPlaceholders<Q>]: Parsed<unknown>;
|
|
16
|
+
}> | undefined, O extends Parsed<unknown> | undefined>(options: {
|
|
17
|
+
input?: I extends ObjectType<any> ? CheckPlaceholders<I, Q> : I;
|
|
18
|
+
query: Q;
|
|
19
|
+
output?: O;
|
|
20
|
+
}): (args: I extends ObjectType<any> ? I["infer"] : void) => Promise<O extends Parsed<unknown> ? O["infer"] : void>;
|
|
21
|
+
}
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=SQLDB.d.ts.map
|