@primate/go 0.6.0 → 0.8.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.
@@ -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
- const run = (wasm, go) => `${COMMAND} build -o ${wasm.name} ${go.name}`;
19
- function postlude(code, route_id) {
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("Go file does not import {0} - skipping route registration", REPO);
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}`)).trim();
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("using {0} package v{1}.{2}.x", REPO, major, minor);
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
- import wrapper from "@primate/go/wrapper";
52
- import bytes from "app:wasm/${route_id}.wasm" with { type: "bytes" };
53
- import i18n from "app:config:i18n";
54
- import session from "app:config:session";
55
- await wrapper(bytes, ${JSON.stringify(route_id)}, { i18n, session });
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 app from "$:app";
56
+ import session from "app:config:session";
57
+ const handlers = await wrapper(bytes, ${JSON.stringify(route_id)}, {
58
+ i18n: app.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 build_go_file = app.runpath("wasm", `${route_id}.go`);
70
- await build_go_file.directory.create();
71
- await build_go_file.write(postlude(await route.text(), route_id));
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("compiling {0} to WebAssembly", route);
75
- await io.run(run(wasm, build_go_file), {
76
- cwd: build_go_file.directory.path,
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 build_go_file.remove();
88
+ await build_file.remove();
84
89
  return wrap(route_id);
85
90
  });
86
91
  });
@@ -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
@@ -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 = Object.fromEntries(Object.keys(errors).map(k => [k, k]));
25
+ export const Code = error.names(errors);
26
26
  export default errors;
27
27
  //# sourceMappingURL=errors.js.map
@@ -1,7 +1,7 @@
1
1
  declare const module: {
2
2
  schema: import("pema").ObjectType<import("pema").NormalizeSchemaObject<{
3
3
  readonly extension: import("pema").OptionalType<import("pema").StringType>;
4
- }>, {
4
+ }>, undefined, {
5
5
  extension: string | undefined;
6
6
  }>;
7
7
  name: string;
@@ -1,45 +1,38 @@
1
- import type { RequestFacade } from "@primate/core";
2
- export default function toRequest(request: RequestFacade): Promise<{
3
- body: {
4
- formSync: () => string;
5
- filesSync: () => {
6
- bytes: Uint8Array;
7
- field: string;
8
- name: string;
9
- size: number;
10
- type: string;
11
- }[];
12
- type: "form";
13
- } | {
14
- textSync: () => string;
15
- type: "text";
16
- jsonSync?: undefined;
17
- binarySync?: undefined;
18
- binaryTypeSync?: undefined;
19
- } | {
20
- jsonSync: () => string;
21
- type: "json";
22
- textSync?: undefined;
23
- binarySync?: undefined;
24
- binaryTypeSync?: undefined;
25
- } | {
26
- binarySync: () => Uint8Array<ArrayBuffer>;
27
- binaryTypeSync: () => string;
28
- type: "binary";
29
- textSync?: undefined;
30
- jsonSync?: undefined;
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
- async function bridge_form(body) {
2
- const meta = Object.create(null);
3
- const files = [];
4
- const pending = [];
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())) {
9
- const name = v.name;
10
- const type = v.type;
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 "json": {
40
- const val = body.json();
41
- const jsonStr = JSON.stringify(val);
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
- return await bridge_form(body);
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 "binary": {
48
- const blob = body.binary();
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 || "application/octet-stream";
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 { type ResponseLike } from "@primate/core/response";
2
- declare const _default: (response: any) => ResponseLike;
3
- export default _default;
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
- export default (response) => {
6
- if (typeof response === "string") {
6
+ function to_response(args) {
7
+ if (is.string(args)) {
7
8
  try {
8
- response = JSON.parse(response);
9
+ args = JSON.parse(args);
9
10
  }
10
11
  catch {
11
- return response;
12
+ return args;
12
13
  }
13
14
  }
14
- if (response && typeof response === "object") {
15
- const handler = response.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 } = response;
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 } = response;
23
+ const { location, status } = args;
23
24
  return redirect(location, status);
24
25
  }
25
- const { options } = response;
26
+ const { options } = args;
26
27
  return error(options ? JSON.parse(options) : {});
27
28
  }
28
29
  }
29
- return response;
30
- };
30
+ return args;
31
+ }
32
+ ;
33
+ export default to_response;
31
34
  //# sourceMappingURL=to-response.js.map
@@ -1,3 +1,3 @@
1
- declare function _default(): void;
1
+ declare const _default: () => void;
2
2
  export default _default;
3
3
  //# sourceMappingURL=wasm_exec.d.ts.map
@@ -12,5 +12,5 @@ declare global {
12
12
  export default function wrapper(bytes: Uint8Array, route_id: string, context: {
13
13
  i18n?: any;
14
14
  session?: any;
15
- }): Promise<void>;
15
+ }): Promise<Record<string, (request: any) => Promise<any>>>;
16
16
  //# sourceMappingURL=wrapper.d.ts.map
@@ -1,7 +1,6 @@
1
1
  import env from "@primate/go/env";
2
- import toRequest from "@primate/go/to-request";
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 verbs = registry?.length
54
- ? Array.from({ length: registry.length }, (_, i) => registry[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
- for (const verb of verbs) {
57
- route[verb.toLowerCase()](async (request) => {
58
- const requested = await toRequest(request);
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.6.0",
3
+ "version": "0.8.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.8.1",
21
- "@rcompat/env": "^0.17.2",
22
- "@rcompat/error": "^0.3.1",
23
- "@rcompat/fs": "^0.28.1",
24
- "@rcompat/io": "^0.5.1",
25
- "@primate/core": "^0.6.0",
26
- "pema": "^0.6.0"
20
+ "@rcompat/assert": "^0.13.0",
21
+ "@rcompat/env": "^0.22.2",
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.8.0",
27
+ "pema": "^0.8.0"
27
28
  },
28
29
  "devDependencies": {
29
- "@rcompat/type": "^0.11.1"
30
+ "@rcompat/type": "^0.17.0"
30
31
  },
31
32
  "peerDependencies": {
32
- "primate": "^0.37.0"
33
+ "primate": "^0.39.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"
@@ -49,9 +51,8 @@
49
51
  }
50
52
  },
51
53
  "scripts": {
52
- "build": "npm run clean && tsc",
54
+ "build": "npm run clean && tsgo",
53
55
  "clean": "rm -rf ./lib",
54
- "lint": "eslint .",
55
- "test": "npx proby"
56
+ "lint": "eslint ."
56
57
  }
57
58
  }