@primate/go 0.2.0 → 0.3.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.
@@ -2,6 +2,6 @@ import Runtime from "#Runtime";
2
2
  import type BuildApp from "@primate/core/BuildApp";
3
3
  import type NextBuild from "@primate/core/NextBuild";
4
4
  export default class Default extends Runtime {
5
- build(app: BuildApp, next: NextBuild): import("@rcompat/type/MaybePromise").default<BuildApp>;
5
+ build(app: BuildApp, next: NextBuild): Promise<BuildApp>;
6
6
  }
7
7
  //# sourceMappingURL=Default.d.ts.map
@@ -1,21 +1,26 @@
1
1
  import Runtime from "#Runtime";
2
2
  import AppError from "@primate/core/AppError";
3
+ import TAG from "@primate/core/backend/TAG";
4
+ import fail from "@primate/core/fail";
5
+ import location from "@primate/core/location";
3
6
  import log from "@primate/core/log";
4
- import wrap from "@primate/core/route/wrap";
5
7
  import assert from "@rcompat/assert";
6
8
  import user from "@rcompat/env/user";
7
9
  import runtime from "@rcompat/runtime";
8
10
  import execute from "@rcompat/stdio/execute";
9
11
  import which from "@rcompat/stdio/which";
10
- const command = await which("go");
11
- const env = {
12
+ const COMMAND = await which("go");
13
+ const ENV = {
12
14
  GOARCH: "wasm",
13
- GOCACHE: (await execute(`${command} env GOCACHE`, {})).replaceAll("\n", ""),
15
+ GOCACHE: (await execute(`${COMMAND} env GOCACHE`, {})).replaceAll("\n", ""),
14
16
  GOOS: "js",
15
17
  HOME: user.HOME,
16
18
  };
17
- const run = (wasm, go) => `${command} build -o ${wasm.name} ${go.name}`;
18
- const js_wrapper = (path) => `
19
+ const REPO = "github.com/primate-run/go";
20
+ const [MAJOR, MINOR] = TAG.split(".").map(Number);
21
+ const run = (wasm, go) => `${COMMAND} build -o ${wasm.name} ${go.name}`;
22
+ function wrapper(path) {
23
+ return `
19
24
  import env from "@primate/go/env";
20
25
  import toRequest from "@primate/go/to-request";
21
26
  import to_response from "@primate/go/to-response";
@@ -50,64 +55,89 @@ globalThis.PRMT_SESSION = {
50
55
  };
51
56
 
52
57
  ${runtime === "bun"
53
- ? `import route_path from "${path}" with { type: "file" };
54
- const go_route = await Bun.file(route_path).arrayBuffer();`
55
- :
56
- `import FileRef from "primate/runtime/FileRef";
57
-
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";
58
61
  const buffer = await FileRef.arrayBuffer(import.meta.url+"/../${path}");
59
- const go_route = new Uint8Array(buffer);`}
62
+ const binary = new Uint8Array(buffer);`}
63
+
60
64
 
61
65
  env();
62
66
 
63
67
  // Run Go once to register routes and get available verbs
64
68
  const go = new globalThis.Go();
65
- const result = await WebAssembly.instantiate(go_route, go.importObject);
69
+ const result = await WebAssembly.instantiate(binary, go.importObject);
66
70
  go.run(result.instance);
67
-
68
71
  const verbs = globalThis.__primate_verbs || [];
69
72
 
70
73
  for (const verb of verbs) {
71
74
  route[verb.toLowerCase()](async request => {
72
75
  const requested = await toRequest(request);
73
- const freshGo = new globalThis.Go();
74
- return WebAssembly.instantiate(go_route, {...freshGo.importObject}).then(freshResult => {
75
- freshGo.run(freshResult.instance);
76
+ const _go = new globalThis.Go();
77
+ return WebAssembly.instantiate(binary, {..._go.importObject}).then(_result => {
78
+ _go.run(_result.instance);
76
79
  return to_response(globalThis.__primate_handle(verb, requested));
77
80
  });
78
81
  });
79
82
  }
80
83
  `;
81
- const go_wrapper = (code) => {
82
- if (!code.includes("github.com/primate-run/go/route")) {
83
- console.warn("Go file does not import \"github.com/primate-run/go/route\" - skipping route registration");
84
+ }
85
+ ;
86
+ function postlude(code) {
87
+ if (!code.includes(`${REPO}/route`)) {
88
+ log.warn("Go file does not import {0} - skipping route registration", REPO);
84
89
  return code;
85
90
  }
86
91
  return `${code}
87
-
88
92
  func main() {
89
93
  route.Commit()
90
94
  select{}
91
95
  }`;
92
- };
96
+ }
97
+ ;
98
+ async function check_version() {
99
+ try {
100
+ const version = await execute(`go list -m -f '{{.Version}}' ${REPO}`);
101
+ const trimmed = version.trim();
102
+ const version_match = trimmed.match(/^v?(\d+)\.(\d+)\.(\d+)/);
103
+ if (!version_match)
104
+ throw fail("invalid version format: {0}", trimmed);
105
+ const [, major, minor] = version_match.map(Number);
106
+ if (major !== MAJOR || minor !== MINOR) {
107
+ throw fail("installed version {0} not in range {1}", trimmed, TAG);
108
+ }
109
+ }
110
+ catch (error) {
111
+ if (error instanceof AppError)
112
+ throw error;
113
+ console.log(error);
114
+ throw fail("{0} dependency not found - run 'go get {0}@v{1}.0'", REPO, TAG);
115
+ }
116
+ }
93
117
  export default class Default extends Runtime {
94
- build(app, next) {
95
- app.bind(this.fileExtension, async (route, { build, context }) => {
118
+ async build(app, next) {
119
+ await check_version();
120
+ app.bind(this.fileExtension, async (route, { context }) => {
96
121
  assert(context === "routes", "go: only route files are supported");
97
- const code = await route.text();
98
- // wrap user code with main function if they imported our route package
99
- await route.write(go_wrapper(code));
100
- const wasm = route.bare(".wasm");
101
- const js_code = wrap(js_wrapper(wasm.name), route, build);
102
- await route.append(".js").write(js_code);
122
+ const relative = route.debase(app.path.routes);
123
+ const basename = relative.path.slice(1, -relative.extension.length);
124
+ const code = postlude(await route.text());
125
+ const build_go_file = app.runpath(location.routes, `${basename}.go`);
126
+ await build_go_file.directory.create({ recursive: true });
127
+ await build_go_file.write(code);
128
+ const wasm = app.runpath(location.routes, `${basename}.wasm`);
103
129
  try {
104
130
  log.info("compiling {0} to WebAssembly", route);
105
- // compile .go to .wasm using user's go.mod from project root
106
- await execute(run(wasm, route), { cwd: `${route.directory}`, env });
131
+ await execute(run(wasm, build_go_file), {
132
+ cwd: build_go_file.directory.path,
133
+ env: ENV,
134
+ });
107
135
  }
108
136
  catch (error) {
109
- throw new AppError("Error in module {0}\n{1}", route, error);
137
+ throw fail("error in module {0}\n{1}", route, error);
110
138
  }
139
+ await build_go_file.remove();
140
+ return wrapper(wasm.name);
111
141
  });
112
142
  return next(app);
113
143
  }
@@ -1,7 +1,7 @@
1
1
  import type RequestFacade from "@primate/core/request/RequestFacade";
2
2
  export default function toRequest(request: RequestFacade): Promise<{
3
3
  body: {
4
- fieldsSync: () => string;
4
+ formSync: () => string;
5
5
  filesSync: () => {
6
6
  bytes: Uint8Array;
7
7
  field: string;
@@ -9,7 +9,7 @@ export default function toRequest(request: RequestFacade): Promise<{
9
9
  size: number;
10
10
  type: string;
11
11
  }[];
12
- type: "fields";
12
+ type: "form";
13
13
  } | {
14
14
  textSync: () => string;
15
15
  type: "text";
@@ -1,19 +1,16 @@
1
- async function bridgeFields(body) {
2
- const fields = body.fields();
1
+ async function bridge_form(body) {
3
2
  const meta = Object.create(null);
4
3
  const files = [];
5
4
  const pending = [];
6
- for (const [k, v] of Object.entries(fields)) {
7
- if (typeof v === "string") {
8
- meta[k] = v;
9
- continue;
10
- }
11
- // v is File
5
+ for (const [k, v] of Object.entries(body.form())) {
6
+ meta[k] = v;
7
+ }
8
+ for (const [k, v] of Object.entries(body.files())) {
12
9
  const name = v.name;
13
10
  const type = v.type;
14
11
  const size = v.size;
15
12
  meta[k] = { name, size, type };
16
- // Precompute bytes so Go can call a SYNC getter
13
+ // precompute bytes so Go can call a SYNC getter
17
14
  pending.push(v.arrayBuffer().then(buffer => {
18
15
  files.push({
19
16
  bytes: new Uint8Array(buffer),
@@ -27,9 +24,9 @@ async function bridgeFields(body) {
27
24
  await Promise.all(pending);
28
25
  const jsonStr = JSON.stringify(meta);
29
26
  return {
30
- fieldsSync: () => jsonStr,
27
+ formSync: () => jsonStr,
31
28
  filesSync: () => files,
32
- type: "fields",
29
+ type: "form",
33
30
  };
34
31
  }
35
32
  async function bridgeBody(body) {
@@ -44,8 +41,8 @@ async function bridgeBody(body) {
44
41
  const jsonStr = JSON.stringify(val);
45
42
  return { jsonSync: () => jsonStr, type };
46
43
  }
47
- case "fields": {
48
- return await bridgeFields(body);
44
+ case "form": {
45
+ return await bridge_form(body);
49
46
  }
50
47
  case "binary": {
51
48
  const blob = body.binary();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primate/go",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Primate Go backend",
5
5
  "homepage": "https://primate.run/docs/backend/go",
6
6
  "bugs": "https://github.com/primate-run/primate/issues",
@@ -23,10 +23,10 @@
23
23
  "@rcompat/runtime": "^0.6.0",
24
24
  "@rcompat/stdio": "^0.12.2",
25
25
  "@rcompat/string": "^0.10.0",
26
- "@primate/core": "^0.2.0"
26
+ "@primate/core": "^0.3.0"
27
27
  },
28
28
  "peerDependencies": {
29
- "primate": "^0.33.0"
29
+ "primate": "^0.34.0"
30
30
  },
31
31
  "type": "module",
32
32
  "imports": {