@primate/go 0.6.0 → 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/default.js +26 -21
- package/lib/private/errors.d.ts +7 -1
- package/lib/private/errors.js +1 -1
- package/lib/private/to-request.d.ts +31 -38
- package/lib/private/to-request.js +39 -54
- package/lib/private/to-response.d.ts +4 -3
- package/lib/private/to-response.js +14 -11
- package/lib/private/wrapper.d.ts +1 -1
- package/lib/private/wrapper.js +10 -10
- package/package.json +13 -12
package/lib/private/default.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import E from "#errors";
|
|
2
2
|
import module from "#module";
|
|
3
|
-
import log from "@primate/core/log";
|
|
4
3
|
import server from "@primate/core/server";
|
|
5
4
|
import assert from "@rcompat/assert";
|
|
6
5
|
import env from "@rcompat/env";
|
|
@@ -15,10 +14,12 @@ const ENV = {
|
|
|
15
14
|
};
|
|
16
15
|
const REPO = "github.com/primate-run/go";
|
|
17
16
|
const [MAJOR, MINOR] = server.TAG.split(".").map(Number);
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
function run(wasm, go) {
|
|
18
|
+
return `${COMMAND} build -o ${wasm.name} ${go.name}`;
|
|
19
|
+
}
|
|
20
|
+
function postlude(app, code, route_id) {
|
|
20
21
|
if (!code.includes(`${REPO}/route`)) {
|
|
21
|
-
log.warn
|
|
22
|
+
app.log.warn `Go file does not import ${REPO}, skipping route registration`;
|
|
22
23
|
return code;
|
|
23
24
|
}
|
|
24
25
|
return `${code}
|
|
@@ -27,9 +28,9 @@ func main() {
|
|
|
27
28
|
select{}
|
|
28
29
|
}`;
|
|
29
30
|
}
|
|
30
|
-
async function check_version() {
|
|
31
|
+
async function check_version(app) {
|
|
31
32
|
try {
|
|
32
|
-
const version = (await io.run(`go list -m -f '{{.Version}}' ${REPO}
|
|
33
|
+
const version = (await io.run(`go list -m -f '{{.Version}}' ${REPO}`, { cwd: app.root.path })).trim();
|
|
33
34
|
const version_match = version.match(/^v?(\d+)\.(\d+)\.(\d+)/);
|
|
34
35
|
if (version_match == null)
|
|
35
36
|
throw E.invalid_version_format(version);
|
|
@@ -37,7 +38,7 @@ async function check_version() {
|
|
|
37
38
|
if (major !== MAJOR || minor !== MINOR) {
|
|
38
39
|
throw E.version_not_in_range(version, server.TAG);
|
|
39
40
|
}
|
|
40
|
-
log.info
|
|
41
|
+
app.log.info `using ${REPO} package v${major}.${minor}.x`;
|
|
41
42
|
}
|
|
42
43
|
catch (error) {
|
|
43
44
|
if (CodeError.is(error))
|
|
@@ -48,12 +49,16 @@ async function check_version() {
|
|
|
48
49
|
}
|
|
49
50
|
function wrap(route_id) {
|
|
50
51
|
return `
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
import route from "primate/route";
|
|
53
|
+
import wrapper from "@primate/go/wrapper";
|
|
54
|
+
import bytes from "app:wasm/${route_id}.wasm" with { type: "bytes" };
|
|
55
|
+
import i18n from "app:config:i18n";
|
|
56
|
+
import session from "app:config:session";
|
|
57
|
+
const handlers = await wrapper(bytes, ${JSON.stringify(route_id)}, {
|
|
58
|
+
i18n, session,
|
|
59
|
+
});
|
|
60
|
+
export default route(handlers);
|
|
61
|
+
`;
|
|
57
62
|
}
|
|
58
63
|
export default function default_module(input = {}) {
|
|
59
64
|
const { extension = module.extension } = module.schema.parse(input);
|
|
@@ -61,26 +66,26 @@ export default function default_module(input = {}) {
|
|
|
61
66
|
name: module.name,
|
|
62
67
|
setup({ onBuild }) {
|
|
63
68
|
onBuild(async (app) => {
|
|
64
|
-
await check_version();
|
|
69
|
+
await check_version(app);
|
|
65
70
|
app.bind(extension, async (route, { context }) => {
|
|
66
71
|
assert.true(context === "routes", E.only_route_files());
|
|
67
72
|
const relative = route.debase(app.path.routes);
|
|
68
73
|
const route_id = relative.path.slice(1, -relative.extension.length);
|
|
69
|
-
const
|
|
70
|
-
await
|
|
71
|
-
await
|
|
74
|
+
const build_file = app.runpath("wasm", `${route_id}.go`);
|
|
75
|
+
await build_file.directory.create();
|
|
76
|
+
await build_file.write(postlude(app, await route.text(), route_id));
|
|
72
77
|
const wasm = app.runpath("wasm", `${route_id}.wasm`);
|
|
73
78
|
try {
|
|
74
|
-
log.info
|
|
75
|
-
await io.run(run(wasm,
|
|
76
|
-
cwd:
|
|
79
|
+
app.log.info `compiling ${(route.debase(app.root))} to WebAssembly`;
|
|
80
|
+
await io.run(run(wasm, build_file), {
|
|
81
|
+
cwd: build_file.directory.path,
|
|
77
82
|
env: ENV,
|
|
78
83
|
});
|
|
79
84
|
}
|
|
80
85
|
catch (error) {
|
|
81
86
|
throw E.backend_error(route, error);
|
|
82
87
|
}
|
|
83
|
-
await
|
|
88
|
+
await build_file.remove();
|
|
84
89
|
return wrap(route_id);
|
|
85
90
|
});
|
|
86
91
|
});
|
package/lib/private/errors.d.ts
CHANGED
|
@@ -11,7 +11,13 @@ declare const errors: {
|
|
|
11
11
|
version_not_in_range: typeof version_not_in_range;
|
|
12
12
|
dependency_not_found: typeof dependency_not_found;
|
|
13
13
|
};
|
|
14
|
+
export declare const Code: {
|
|
15
|
+
backend_error: "backend_error";
|
|
16
|
+
only_route_files: "only_route_files";
|
|
17
|
+
invalid_version_format: "invalid_version_format";
|
|
18
|
+
version_not_in_range: "version_not_in_range";
|
|
19
|
+
dependency_not_found: "dependency_not_found";
|
|
20
|
+
};
|
|
14
21
|
export type Code = keyof typeof errors;
|
|
15
|
-
export declare const Code: { [K in Code]: K; };
|
|
16
22
|
export default errors;
|
|
17
23
|
//# sourceMappingURL=errors.d.ts.map
|
package/lib/private/errors.js
CHANGED
|
@@ -22,6 +22,6 @@ const errors = error.coded({
|
|
|
22
22
|
version_not_in_range,
|
|
23
23
|
dependency_not_found,
|
|
24
24
|
});
|
|
25
|
-
export const Code =
|
|
25
|
+
export const Code = error.names(errors);
|
|
26
26
|
export default errors;
|
|
27
27
|
//# sourceMappingURL=errors.js.map
|
|
@@ -1,45 +1,38 @@
|
|
|
1
|
-
import type { RequestFacade } from "@primate/core";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
} | {
|
|
32
|
-
type: "none";
|
|
33
|
-
textSync?: undefined;
|
|
34
|
-
jsonSync?: undefined;
|
|
35
|
-
binarySync?: undefined;
|
|
36
|
-
binaryTypeSync?: undefined;
|
|
37
|
-
};
|
|
1
|
+
import type { RequestContentType, RequestFacade } from "@primate/core";
|
|
2
|
+
type FileEntry = {
|
|
3
|
+
bytes: Uint8Array;
|
|
4
|
+
field: string;
|
|
5
|
+
name: string;
|
|
6
|
+
size: number;
|
|
7
|
+
type: string;
|
|
8
|
+
};
|
|
9
|
+
type BridgedBody = {
|
|
10
|
+
type: "json";
|
|
11
|
+
jsonSync: () => string;
|
|
12
|
+
} | {
|
|
13
|
+
type: "text";
|
|
14
|
+
textSync: () => string;
|
|
15
|
+
} | {
|
|
16
|
+
type: "form";
|
|
17
|
+
formSync: () => string;
|
|
18
|
+
} | {
|
|
19
|
+
type: "multipart";
|
|
20
|
+
formSync: () => string;
|
|
21
|
+
filesSync: () => FileEntry[];
|
|
22
|
+
} | {
|
|
23
|
+
type: "blob";
|
|
24
|
+
blobSync: () => Uint8Array;
|
|
25
|
+
blobTypeSync: () => string;
|
|
26
|
+
} | {
|
|
27
|
+
type: "none";
|
|
28
|
+
};
|
|
29
|
+
export default function toRequest(request: RequestFacade, contentType: RequestContentType | ""): Promise<{
|
|
30
|
+
body: BridgedBody;
|
|
38
31
|
cookies: string;
|
|
39
32
|
headers: string;
|
|
40
33
|
path: string;
|
|
41
34
|
query: string;
|
|
42
|
-
searchParams: string;
|
|
43
35
|
url: URL;
|
|
44
36
|
}>;
|
|
37
|
+
export {};
|
|
45
38
|
//# sourceMappingURL=to-request.d.ts.map
|
|
@@ -1,72 +1,57 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const size = v.size;
|
|
12
|
-
meta[k] = { name, size, type };
|
|
13
|
-
// precompute bytes so Go can call a sync getter
|
|
14
|
-
pending.push(v.arrayBuffer().then(buffer => {
|
|
15
|
-
files.push({
|
|
16
|
-
bytes: new Uint8Array(buffer),
|
|
17
|
-
field: k,
|
|
18
|
-
name,
|
|
19
|
-
size,
|
|
20
|
-
type,
|
|
21
|
-
});
|
|
22
|
-
}));
|
|
23
|
-
}
|
|
24
|
-
await Promise.all(pending);
|
|
25
|
-
const jsonStr = JSON.stringify(meta);
|
|
26
|
-
return {
|
|
27
|
-
formSync: () => jsonStr,
|
|
28
|
-
filesSync: () => files,
|
|
29
|
-
type: "form",
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
async function bridgeBody(body) {
|
|
33
|
-
const type = body.type;
|
|
34
|
-
switch (type) {
|
|
35
|
-
case "text": {
|
|
36
|
-
const s = body.text();
|
|
37
|
-
return { textSync: () => s, type };
|
|
1
|
+
import is from "@rcompat/is";
|
|
2
|
+
async function bridgeBody(body, contentType) {
|
|
3
|
+
switch (contentType) {
|
|
4
|
+
case "text/plain": {
|
|
5
|
+
const s = await body.text();
|
|
6
|
+
return { type: "text", textSync: () => s };
|
|
7
|
+
}
|
|
8
|
+
case "application/json": {
|
|
9
|
+
const val = await body.json();
|
|
10
|
+
return { type: "json", jsonSync: () => JSON.stringify(val) };
|
|
38
11
|
}
|
|
39
|
-
case "
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
return { jsonSync: () => jsonStr, type };
|
|
12
|
+
case "application/x-www-form-urlencoded": {
|
|
13
|
+
const form = await body.form();
|
|
14
|
+
return { type: "form", formSync: () => JSON.stringify(form) };
|
|
43
15
|
}
|
|
44
|
-
case "form": {
|
|
45
|
-
|
|
16
|
+
case "multipart/form-data": {
|
|
17
|
+
const result = await body.multipart();
|
|
18
|
+
const pending = [];
|
|
19
|
+
const bridgedFiles = [];
|
|
20
|
+
for (const [field, file] of Object.entries(result.files)) {
|
|
21
|
+
const name = file.name;
|
|
22
|
+
const type = file.type;
|
|
23
|
+
const size = file.size;
|
|
24
|
+
pending.push(file.arrayBuffer().then(buffer => {
|
|
25
|
+
bridgedFiles.push({
|
|
26
|
+
bytes: new Uint8Array(buffer),
|
|
27
|
+
field,
|
|
28
|
+
name,
|
|
29
|
+
size,
|
|
30
|
+
type,
|
|
31
|
+
});
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
await Promise.all(pending);
|
|
35
|
+
return { type: "multipart", formSync: () => JSON.stringify(result.form), filesSync: () => bridgedFiles };
|
|
46
36
|
}
|
|
47
|
-
case "
|
|
48
|
-
const blob = body.
|
|
37
|
+
case "application/octet-stream": {
|
|
38
|
+
const blob = await body.blob();
|
|
49
39
|
const buf = new Uint8Array(await blob.arrayBuffer());
|
|
50
|
-
const mime = blob.type
|
|
51
|
-
return {
|
|
52
|
-
binarySync: () => buf,
|
|
53
|
-
binaryTypeSync: () => mime,
|
|
54
|
-
type,
|
|
55
|
-
};
|
|
40
|
+
const mime = is.text(blob.type) ? blob.type : "application/octet-stream";
|
|
41
|
+
return { type: "blob", blobSync: () => buf, blobTypeSync: () => mime };
|
|
56
42
|
}
|
|
57
43
|
default:
|
|
58
44
|
return { type: "none" };
|
|
59
45
|
}
|
|
60
46
|
}
|
|
61
|
-
export default async function toRequest(request) {
|
|
62
|
-
const body = await bridgeBody(request.body);
|
|
47
|
+
export default async function toRequest(request, contentType) {
|
|
48
|
+
const body = await bridgeBody(request.body, contentType);
|
|
63
49
|
return {
|
|
64
50
|
body,
|
|
65
51
|
cookies: JSON.stringify(request.cookies.toJSON()),
|
|
66
52
|
headers: JSON.stringify(request.headers.toJSON()),
|
|
67
53
|
path: JSON.stringify(request.path.toJSON()),
|
|
68
54
|
query: JSON.stringify(request.query.toJSON()),
|
|
69
|
-
searchParams: JSON.stringify(request.url.searchParams),
|
|
70
55
|
url: request.url,
|
|
71
56
|
};
|
|
72
57
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type { ResponseLike } from "@primate/core";
|
|
2
|
+
import type { Dict } from "@rcompat/type";
|
|
3
|
+
declare function to_response(args: Dict | string): ResponseLike;
|
|
4
|
+
export default to_response;
|
|
4
5
|
//# sourceMappingURL=to-response.d.ts.map
|
|
@@ -1,31 +1,34 @@
|
|
|
1
1
|
import response from "@primate/core/response";
|
|
2
|
+
import is from "@rcompat/is";
|
|
2
3
|
const { error, redirect, view } = response;
|
|
3
4
|
const handlers = { error, redirect, view };
|
|
4
5
|
const is_handler = (handler) => typeof handler === "string" && Object.keys(handlers).includes(handler);
|
|
5
|
-
|
|
6
|
-
if (
|
|
6
|
+
function to_response(args) {
|
|
7
|
+
if (is.string(args)) {
|
|
7
8
|
try {
|
|
8
|
-
|
|
9
|
+
args = JSON.parse(args);
|
|
9
10
|
}
|
|
10
11
|
catch {
|
|
11
|
-
return
|
|
12
|
+
return args;
|
|
12
13
|
}
|
|
13
14
|
}
|
|
14
|
-
if (
|
|
15
|
-
const handler =
|
|
15
|
+
if (is.dict(args)) {
|
|
16
|
+
const handler = args.handler;
|
|
16
17
|
if (is_handler(handler)) {
|
|
17
18
|
if (handler === "view") {
|
|
18
|
-
const { component, props, options } =
|
|
19
|
+
const { component, props, options } = args;
|
|
19
20
|
return view(component, props ? JSON.parse(props) : {}, options ? JSON.parse(options) : {});
|
|
20
21
|
}
|
|
21
22
|
if (handler === "redirect") {
|
|
22
|
-
const { location, status } =
|
|
23
|
+
const { location, status } = args;
|
|
23
24
|
return redirect(location, status);
|
|
24
25
|
}
|
|
25
|
-
const { options } =
|
|
26
|
+
const { options } = args;
|
|
26
27
|
return error(options ? JSON.parse(options) : {});
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
|
-
return
|
|
30
|
-
}
|
|
30
|
+
return args;
|
|
31
|
+
}
|
|
32
|
+
;
|
|
33
|
+
export default to_response;
|
|
31
34
|
//# sourceMappingURL=to-response.js.map
|
package/lib/private/wrapper.d.ts
CHANGED
package/lib/private/wrapper.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import env from "@primate/go/env";
|
|
2
|
-
import
|
|
2
|
+
import to_request from "@primate/go/to-request";
|
|
3
3
|
import to_response from "@primate/go/to-response";
|
|
4
|
-
import route from "primate/route";
|
|
5
4
|
export default async function wrapper(bytes, route_id, context) {
|
|
6
5
|
if (!globalThis.__primate_go_initialized) {
|
|
7
6
|
globalThis.__primate_go_initialized = new Set();
|
|
@@ -50,17 +49,18 @@ export default async function wrapper(bytes, route_id, context) {
|
|
|
50
49
|
});
|
|
51
50
|
const registry_fn = globalThis[registry_name];
|
|
52
51
|
const registry = registry_fn();
|
|
53
|
-
const
|
|
54
|
-
? Array.from({ length: registry.length }, (_, i) =>
|
|
52
|
+
const routes = registry?.length
|
|
53
|
+
? Array.from({ length: registry.length }, (_, i) => ({
|
|
54
|
+
verb: registry[i].verb,
|
|
55
|
+
contentType: registry[i].contentType,
|
|
56
|
+
}))
|
|
55
57
|
: [];
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const requested = await
|
|
58
|
+
globalThis.__primate_go_initialized.add(route_id);
|
|
59
|
+
return Object.fromEntries(routes.map(({ verb, contentType }) => [verb.toLowerCase(), async (request) => {
|
|
60
|
+
const requested = await to_request(request, contentType);
|
|
59
61
|
const callFn = globalThis[call_go];
|
|
60
62
|
const response = callFn(verb, requested);
|
|
61
63
|
return to_response(response);
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
globalThis.__primate_go_initialized.add(route_id);
|
|
64
|
+
}]));
|
|
65
65
|
}
|
|
66
66
|
//# sourceMappingURL=wrapper.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primate/go",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Go backend for Primate",
|
|
5
5
|
"homepage": "https://primate.run/docs/backend/go",
|
|
6
6
|
"bugs": "https://github.com/primate-run/primate/issues",
|
|
@@ -17,21 +17,23 @@
|
|
|
17
17
|
"!/**/*.spec.*"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@rcompat/assert": "^0.
|
|
21
|
-
"@rcompat/env": "^0.
|
|
22
|
-
"@rcompat/error": "^0.
|
|
23
|
-
"@rcompat/fs": "^0.
|
|
24
|
-
"@rcompat/io": "^0.
|
|
25
|
-
"@
|
|
26
|
-
"
|
|
20
|
+
"@rcompat/assert": "^0.13.0",
|
|
21
|
+
"@rcompat/env": "^0.22.0",
|
|
22
|
+
"@rcompat/error": "^0.8.0",
|
|
23
|
+
"@rcompat/fs": "^0.33.2",
|
|
24
|
+
"@rcompat/io": "^0.10.0",
|
|
25
|
+
"@rcompat/is": "^0.11.0",
|
|
26
|
+
"@primate/core": "^0.7.0",
|
|
27
|
+
"pema": "^0.7.0"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
|
-
"@rcompat/type": "^0.
|
|
30
|
+
"@rcompat/type": "^0.17.0"
|
|
30
31
|
},
|
|
31
32
|
"peerDependencies": {
|
|
32
|
-
"primate": "^0.
|
|
33
|
+
"primate": "^0.38.0"
|
|
33
34
|
},
|
|
34
35
|
"imports": {
|
|
36
|
+
"#vendored/wasm_exec": "./src/private/vendored/wasm_exec.js",
|
|
35
37
|
"#*": {
|
|
36
38
|
"@primate/source": "./src/private/*.ts",
|
|
37
39
|
"default": "./lib/private/*.js"
|
|
@@ -51,7 +53,6 @@
|
|
|
51
53
|
"scripts": {
|
|
52
54
|
"build": "npm run clean && tsc",
|
|
53
55
|
"clean": "rm -rf ./lib",
|
|
54
|
-
"lint": "eslint ."
|
|
55
|
-
"test": "npx proby"
|
|
56
|
+
"lint": "eslint ."
|
|
56
57
|
}
|
|
57
58
|
}
|