@primate/go 0.3.0 → 0.5.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 -87
- package/lib/private/to-request.d.ts +1 -1
- package/lib/private/to-request.js +1 -1
- package/lib/private/to-response.d.ts +2 -7
- package/lib/private/to-response.js +27 -20
- package/lib/private/wrapper.d.ts +16 -0
- package/lib/private/wrapper.js +66 -0
- package/lib/public/wrapper.d.ts +2 -0
- package/lib/public/wrapper.js +2 -0
- package/package.json +17 -17
package/lib/private/Default.js
CHANGED
|
@@ -2,102 +2,34 @@ import Runtime from "#Runtime";
|
|
|
2
2
|
import AppError from "@primate/core/AppError";
|
|
3
3
|
import TAG from "@primate/core/backend/TAG";
|
|
4
4
|
import fail from "@primate/core/fail";
|
|
5
|
-
import location from "@primate/core/location";
|
|
6
5
|
import log from "@primate/core/log";
|
|
7
6
|
import assert from "@rcompat/assert";
|
|
8
7
|
import user from "@rcompat/env/user";
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
import which from "@rcompat/stdio/which";
|
|
12
|
-
const COMMAND = await which("go");
|
|
8
|
+
import io from "@rcompat/io";
|
|
9
|
+
const COMMAND = await io.which("go");
|
|
13
10
|
const ENV = {
|
|
14
11
|
GOARCH: "wasm",
|
|
15
|
-
GOCACHE: (await
|
|
12
|
+
GOCACHE: (await io.run(`${COMMAND} env GOCACHE`, {})).replaceAll("\n", ""),
|
|
16
13
|
GOOS: "js",
|
|
17
14
|
HOME: user.HOME,
|
|
18
15
|
};
|
|
19
16
|
const REPO = "github.com/primate-run/go";
|
|
20
17
|
const [MAJOR, MINOR] = TAG.split(".").map(Number);
|
|
21
18
|
const run = (wasm, go) => `${COMMAND} build -o ${wasm.name} ${go.name}`;
|
|
22
|
-
function
|
|
23
|
-
return `
|
|
24
|
-
import env from "@primate/go/env";
|
|
25
|
-
import toRequest from "@primate/go/to-request";
|
|
26
|
-
import to_response from "@primate/go/to-response";
|
|
27
|
-
import session from "#session";
|
|
28
|
-
import route from "primate/route";
|
|
29
|
-
|
|
30
|
-
globalThis.PRMT_SESSION = {
|
|
31
|
-
get exists() {
|
|
32
|
-
return session.exists;
|
|
33
|
-
},
|
|
34
|
-
get id() {
|
|
35
|
-
return session.id;
|
|
36
|
-
},
|
|
37
|
-
get data() {
|
|
38
|
-
return JSON.stringify(session.try());
|
|
39
|
-
},
|
|
40
|
-
create(data) {
|
|
41
|
-
session.create(JSON.parse(data));
|
|
42
|
-
},
|
|
43
|
-
get() {
|
|
44
|
-
return JSON.stringify(session.get());
|
|
45
|
-
},
|
|
46
|
-
try() {
|
|
47
|
-
return JSON.stringify(session.try());
|
|
48
|
-
},
|
|
49
|
-
set(data) {
|
|
50
|
-
session.set(JSON.parse(data));
|
|
51
|
-
},
|
|
52
|
-
destroy() {
|
|
53
|
-
session.destroy();
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
${runtime === "bun"
|
|
58
|
-
? `import route_path from "./${path}" with { type: "file" };
|
|
59
|
-
const binary = await Bun.file(route_path).arrayBuffer();`
|
|
60
|
-
: `import FileRef from "primate/runtime/FileRef";
|
|
61
|
-
const buffer = await FileRef.arrayBuffer(import.meta.url+"/../${path}");
|
|
62
|
-
const binary = new Uint8Array(buffer);`}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
env();
|
|
66
|
-
|
|
67
|
-
// Run Go once to register routes and get available verbs
|
|
68
|
-
const go = new globalThis.Go();
|
|
69
|
-
const result = await WebAssembly.instantiate(binary, go.importObject);
|
|
70
|
-
go.run(result.instance);
|
|
71
|
-
const verbs = globalThis.__primate_verbs || [];
|
|
72
|
-
|
|
73
|
-
for (const verb of verbs) {
|
|
74
|
-
route[verb.toLowerCase()](async request => {
|
|
75
|
-
const requested = await toRequest(request);
|
|
76
|
-
const _go = new globalThis.Go();
|
|
77
|
-
return WebAssembly.instantiate(binary, {..._go.importObject}).then(_result => {
|
|
78
|
-
_go.run(_result.instance);
|
|
79
|
-
return to_response(globalThis.__primate_handle(verb, requested));
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
`;
|
|
84
|
-
}
|
|
85
|
-
;
|
|
86
|
-
function postlude(code) {
|
|
19
|
+
function postlude(code, route_id) {
|
|
87
20
|
if (!code.includes(`${REPO}/route`)) {
|
|
88
21
|
log.warn("Go file does not import {0} - skipping route registration", REPO);
|
|
89
22
|
return code;
|
|
90
23
|
}
|
|
91
24
|
return `${code}
|
|
92
|
-
func main() {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}`;
|
|
25
|
+
func main() {
|
|
26
|
+
route.Commit("${route_id}")
|
|
27
|
+
select{}
|
|
28
|
+
}`;
|
|
96
29
|
}
|
|
97
|
-
;
|
|
98
30
|
async function check_version() {
|
|
99
31
|
try {
|
|
100
|
-
const version = await
|
|
32
|
+
const version = await io.run(`go list -m -f '{{.Version}}' ${REPO}`);
|
|
101
33
|
const trimmed = version.trim();
|
|
102
34
|
const version_match = trimmed.match(/^v?(\d+)\.(\d+)\.(\d+)/);
|
|
103
35
|
if (!version_match)
|
|
@@ -106,11 +38,11 @@ async function check_version() {
|
|
|
106
38
|
if (major !== MAJOR || minor !== MINOR) {
|
|
107
39
|
throw fail("installed version {0} not in range {1}", trimmed, TAG);
|
|
108
40
|
}
|
|
41
|
+
log.info("using {0} package v{1}.{2}.x", REPO, major, minor);
|
|
109
42
|
}
|
|
110
43
|
catch (error) {
|
|
111
44
|
if (error instanceof AppError)
|
|
112
45
|
throw error;
|
|
113
|
-
console.log(error);
|
|
114
46
|
throw fail("{0} dependency not found - run 'go get {0}@v{1}.0'", REPO, TAG);
|
|
115
47
|
}
|
|
116
48
|
}
|
|
@@ -118,17 +50,18 @@ export default class Default extends Runtime {
|
|
|
118
50
|
async build(app, next) {
|
|
119
51
|
await check_version();
|
|
120
52
|
app.bind(this.fileExtension, async (route, { context }) => {
|
|
121
|
-
assert(context === "routes", "go: only route files are supported");
|
|
53
|
+
assert.true(context === "routes", "go: only route files are supported");
|
|
122
54
|
const relative = route.debase(app.path.routes);
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
await build_go_file.
|
|
128
|
-
|
|
55
|
+
const route_id = relative.path.slice(1, -relative.extension.length);
|
|
56
|
+
// create a temporary .go file in the build directory for compilation
|
|
57
|
+
// this is necessary because `go build` requires an actual file on disk
|
|
58
|
+
const build_go_file = app.runpath("wasm", `${route_id}.go`);
|
|
59
|
+
await build_go_file.directory.create();
|
|
60
|
+
await build_go_file.write(postlude(await route.text(), route_id));
|
|
61
|
+
const wasm = app.runpath("wasm", `${route_id}.wasm`);
|
|
129
62
|
try {
|
|
130
63
|
log.info("compiling {0} to WebAssembly", route);
|
|
131
|
-
await
|
|
64
|
+
await io.run(run(wasm, build_go_file), {
|
|
132
65
|
cwd: build_go_file.directory.path,
|
|
133
66
|
env: ENV,
|
|
134
67
|
});
|
|
@@ -137,7 +70,13 @@ export default class Default extends Runtime {
|
|
|
137
70
|
throw fail("error in module {0}\n{1}", route, error);
|
|
138
71
|
}
|
|
139
72
|
await build_go_file.remove();
|
|
140
|
-
return
|
|
73
|
+
return `
|
|
74
|
+
import wrapper from "@primate/go/wrapper";
|
|
75
|
+
import bytes from "app:wasm/${route_id}.wasm" with { type: "bytes" };
|
|
76
|
+
import i18n from "app:config:i18n";
|
|
77
|
+
import session from "app:config:session";
|
|
78
|
+
await wrapper(bytes, ${JSON.stringify(route_id)}, { i18n, session });
|
|
79
|
+
`;
|
|
141
80
|
});
|
|
142
81
|
return next(app);
|
|
143
82
|
}
|
|
@@ -10,7 +10,7 @@ async function bridge_form(body) {
|
|
|
10
10
|
const type = v.type;
|
|
11
11
|
const size = v.size;
|
|
12
12
|
meta[k] = { name, size, type };
|
|
13
|
-
// precompute bytes so Go can call a
|
|
13
|
+
// precompute bytes so Go can call a sync getter
|
|
14
14
|
pending.push(v.arrayBuffer().then(buffer => {
|
|
15
15
|
files.push({
|
|
16
16
|
bytes: new Uint8Array(buffer),
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import type ResponseLike from "@primate/core/response
|
|
2
|
-
|
|
3
|
-
type Handler = "error" | "redirect" | "view";
|
|
4
|
-
type ResponseType = (() => {
|
|
5
|
-
handler: Handler;
|
|
6
|
-
} & Dict) | string;
|
|
7
|
-
declare const _default: (response: ResponseType) => ResponseLike;
|
|
1
|
+
import { type ResponseLike } from "@primate/core/response";
|
|
2
|
+
declare const _default: (response: any) => ResponseLike;
|
|
8
3
|
export default _default;
|
|
9
4
|
//# sourceMappingURL=to-response.d.ts.map
|
|
@@ -1,24 +1,31 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const handle_handler = (handler, response) => {
|
|
6
|
-
if (handler === "view") {
|
|
7
|
-
const { component, options, props } = response;
|
|
8
|
-
return view(component, parse(props), parse(options));
|
|
9
|
-
}
|
|
10
|
-
if (handler === "redirect") {
|
|
11
|
-
const { location, status } = response;
|
|
12
|
-
return redirect(location, status === null ? undefined : status);
|
|
13
|
-
}
|
|
14
|
-
const { options } = response;
|
|
15
|
-
return error(parse(options));
|
|
16
|
-
};
|
|
1
|
+
import response from "@primate/core/response";
|
|
2
|
+
const { error, redirect, view } = response;
|
|
3
|
+
const handlers = { error, redirect, view };
|
|
4
|
+
const is_handler = (handler) => typeof handler === "string" && Object.keys(handlers).includes(handler);
|
|
17
5
|
export default (response) => {
|
|
18
|
-
if (typeof response === "
|
|
19
|
-
|
|
20
|
-
|
|
6
|
+
if (typeof response === "string") {
|
|
7
|
+
try {
|
|
8
|
+
response = JSON.parse(response);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return response;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
if (response && typeof response === "object") {
|
|
15
|
+
const handler = response.handler;
|
|
16
|
+
if (is_handler(handler)) {
|
|
17
|
+
if (handler === "view") {
|
|
18
|
+
const { component, props, options } = response;
|
|
19
|
+
return view(component, props ? JSON.parse(props) : {}, options ? JSON.parse(options) : {});
|
|
20
|
+
}
|
|
21
|
+
if (handler === "redirect") {
|
|
22
|
+
const { location, status } = response;
|
|
23
|
+
return redirect(location, status);
|
|
24
|
+
}
|
|
25
|
+
const { options } = response;
|
|
26
|
+
return error(options ? JSON.parse(options) : {});
|
|
27
|
+
}
|
|
21
28
|
}
|
|
22
|
-
return
|
|
29
|
+
return response;
|
|
23
30
|
};
|
|
24
31
|
//# sourceMappingURL=to-response.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
var Go: {
|
|
3
|
+
new (): {
|
|
4
|
+
importObject: WebAssembly.Imports;
|
|
5
|
+
run(instance: WebAssembly.Instance): Promise<void>;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
var PRMT_SESSION: any;
|
|
9
|
+
var PRMT_I18N: any;
|
|
10
|
+
var __primate_go_initialized: Set<string> | undefined;
|
|
11
|
+
}
|
|
12
|
+
export default function wrapper(bytes: Uint8Array, route_id: string, context: {
|
|
13
|
+
i18n?: any;
|
|
14
|
+
session?: any;
|
|
15
|
+
}): Promise<void>;
|
|
16
|
+
//# sourceMappingURL=wrapper.d.ts.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import env from "@primate/go/env";
|
|
2
|
+
import toRequest from "@primate/go/to-request";
|
|
3
|
+
import to_response from "@primate/go/to-response";
|
|
4
|
+
import route from "primate/route";
|
|
5
|
+
export default async function wrapper(bytes, route_id, context) {
|
|
6
|
+
if (!globalThis.__primate_go_initialized) {
|
|
7
|
+
globalThis.__primate_go_initialized = new Set();
|
|
8
|
+
}
|
|
9
|
+
if (context.session !== undefined) {
|
|
10
|
+
const session = context.session;
|
|
11
|
+
globalThis.PRMT_SESSION = {
|
|
12
|
+
get exists() { return session.exists; },
|
|
13
|
+
get id() { return session.id; },
|
|
14
|
+
get data() { return JSON.stringify(session.try()); },
|
|
15
|
+
create(data) { session.create(JSON.parse(data)); },
|
|
16
|
+
get() { return JSON.stringify(session.get()); },
|
|
17
|
+
try() { return JSON.stringify(session.try()); },
|
|
18
|
+
set(data) { session.set(JSON.parse(data)); },
|
|
19
|
+
destroy() { session.destroy(); },
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (context.i18n !== undefined) {
|
|
23
|
+
const i18n = context.i18n;
|
|
24
|
+
globalThis.PRMT_I18N = {
|
|
25
|
+
get locale() { return i18n.locale.get(); },
|
|
26
|
+
t(key, params) {
|
|
27
|
+
if (!params)
|
|
28
|
+
return i18n(key);
|
|
29
|
+
return i18n(key, JSON.parse(params));
|
|
30
|
+
},
|
|
31
|
+
set(locale) { i18n.locale.set(locale); },
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
env();
|
|
35
|
+
const safe_route_id = route_id.replace(/\//g, "_");
|
|
36
|
+
const registry_name = `__primate_go_registry_${safe_route_id}`;
|
|
37
|
+
const call_go = `__primate_call_go_${safe_route_id}`;
|
|
38
|
+
delete globalThis[registry_name];
|
|
39
|
+
delete globalThis[call_go];
|
|
40
|
+
await new Promise((resolve) => {
|
|
41
|
+
globalThis[`__primate_go_ready_${safe_route_id}`] = () => {
|
|
42
|
+
resolve();
|
|
43
|
+
};
|
|
44
|
+
const go = new globalThis.Go();
|
|
45
|
+
WebAssembly.instantiate(bytes, go.importObject).then(result => {
|
|
46
|
+
go.run(result.instance).catch(err => {
|
|
47
|
+
console.error("Go runtime error:", route_id, err);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
const registry_fn = globalThis[registry_name];
|
|
52
|
+
const registry = registry_fn();
|
|
53
|
+
const verbs = registry?.length
|
|
54
|
+
? Array.from({ length: registry.length }, (_, i) => registry[i])
|
|
55
|
+
: [];
|
|
56
|
+
for (const verb of verbs) {
|
|
57
|
+
route[verb.toLowerCase()](async (request) => {
|
|
58
|
+
const requested = await toRequest(request);
|
|
59
|
+
const callFn = globalThis[call_go];
|
|
60
|
+
const response = callFn(verb, requested);
|
|
61
|
+
return to_response(response);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
globalThis.__primate_go_initialized.add(route_id);
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=wrapper.js.map
|
package/package.json
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primate/go",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.5.0",
|
|
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",
|
|
7
|
+
"type": "module",
|
|
7
8
|
"license": "MIT",
|
|
8
|
-
"files": [
|
|
9
|
-
"/lib/**/*.js",
|
|
10
|
-
"/lib/**/*.d.ts",
|
|
11
|
-
"!/**/*.spec.*"
|
|
12
|
-
],
|
|
13
9
|
"repository": {
|
|
14
10
|
"type": "git",
|
|
15
11
|
"url": "https://github.com/primate-run/primate",
|
|
16
12
|
"directory": "packages/go"
|
|
17
13
|
},
|
|
14
|
+
"files": [
|
|
15
|
+
"/lib/**/*.js",
|
|
16
|
+
"/lib/**/*.d.ts",
|
|
17
|
+
"!/**/*.spec.*"
|
|
18
|
+
],
|
|
18
19
|
"dependencies": {
|
|
19
|
-
"@rcompat/assert": "^0.
|
|
20
|
-
"@rcompat/
|
|
21
|
-
"@rcompat/
|
|
22
|
-
"@rcompat/
|
|
23
|
-
"@
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"@
|
|
20
|
+
"@rcompat/assert": "^0.6.0",
|
|
21
|
+
"@rcompat/env": "^0.13.0",
|
|
22
|
+
"@rcompat/fs": "^0.25.2",
|
|
23
|
+
"@rcompat/io": "^0.3.0",
|
|
24
|
+
"@primate/core": "^0.5.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@rcompat/type": "^0.9.0"
|
|
27
28
|
},
|
|
28
29
|
"peerDependencies": {
|
|
29
|
-
"primate": "^0.
|
|
30
|
+
"primate": "^0.36.0"
|
|
30
31
|
},
|
|
31
|
-
"type": "module",
|
|
32
32
|
"imports": {
|
|
33
33
|
"#*": {
|
|
34
34
|
"apekit": "./src/private/*.ts",
|