diff --git a/CHANGELOG.md b/CHANGELOG.md index 8476681..0db1619 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # CHANGELOG - go/js +## v1.5.7 (2026-06-28) +- **低代码 TS 文档对齐**: + - `Doc()` 生成的模块入口改为顶层全局变量声明,如 `declare const api: Api_Module;`,不再使用 `declare const go: { ... }` 包裹。 + - 为运行时注入的日志对象补充 `Logger` 接口,仅暴露 `Debug`、`Info`、`Warning`、`Error` 四个 JS 侧需要的方法。 + - 统一非项目类型的不透明命名风格,默认声明改为 `GoContext`、`GoHTTPRequest`、`GoHTTPResponse`、`GoURL`,标准库结构体采用 `GoTime` 这类更稳定的命名。 +- **测试增强**: + - `TestDocGeneration` 新增对顶层全局模块声明、`Logger` 暴露面和 `GoTime` 命名的覆盖。 + ## v1.5.6 (2026-06-21) - **可变参数桥接修复**: `wrapGoFunc` 修复对 Go 可变参数(`...any`)的桥接处理。之前将 JS 剩余参数错误打包为单一切片元素,导致 `Call` 二次嵌套;现在改为逐个追加到 `goArgs` 尾部,由 `reflect.Call` 自动构建可变切片,确保 JS 调用 `redis.Do('HSET', a, b, c)` 正确展开。 - **新增测试**: `TestBridgeVariadic` 覆盖可变参数 0/1/多参数场景。 diff --git a/README.md b/README.md index 56a58a9..b2ab57b 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,25 @@ dts := js.Doc() // Feed d.ts to LLM to provide coding context ``` +The generated declarations are optimized for low-code editors: +- bridged modules are exposed as top-level globals such as `api`, `cast`, `db`, `service` +- injected logger values are typed as `Logger` +- opaque runtime handles use stable names such as `GoContext`, `GoHTTPRequest`, `GoHTTPResponse`, `GoURL` +- exposed standard-library structs use stable names such as `GoTime` + +Example: + +```ts +declare const service: Service_Module + +interface Logger { + Debug(message: string, ...extra: any[]): void + Info(message: string, ...extra: any[]): void + Warning(message: string, ...extra: any[]): void + Error(message: string, ...extra: any[]): void +} +``` + ## Internal Bridge Details The engine uses `goja`'s Host Object mechanism. When a Go struct/pointer is returned to JS, it remains a Go object. When passed back to a Go function, the original pointer is preserved, ensuring zero data loss and state consistency. diff --git a/TEST.md b/TEST.md index 75fba84..0facdcf 100644 --- a/TEST.md +++ b/TEST.md @@ -1,15 +1,15 @@ # Test Report - go/js ## Performance (Benchmark) -Date: 2026-06-21 +Date: 2026-06-28 OS: darwin Arch: amd64 CPU: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz | Benchmark | Iterations | Time/op | |-----------|------------|---------| -| BenchmarkCall | 661954 | 1566 ns/op | -| BenchmarkSync | 51656 | 50748 ns/op | +| BenchmarkCall | 766462 | 1331 ns/op | +| BenchmarkSync | 31066 | 52789 ns/op | *Note: BenchmarkCall covers the hot path of executing a JS function from the pool. BenchmarkSync covers the cost of defining new code (including VM sync).* @@ -31,6 +31,8 @@ CPU: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz --- PASS: TestBridgeMixedInjection (0.00s) === RUN TestBridgeOptionalParams --- PASS: TestBridgeOptionalParams (0.00s) +=== RUN TestBridgeVariadic +--- PASS: TestBridgeVariadic (0.00s) === RUN TestDocGeneration --- PASS: TestDocGeneration (0.00s) === RUN TestPoolVersioning @@ -48,7 +50,7 @@ CPU: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz === RUN TestDefineRedefine --- PASS: TestDefineRedefine (0.00s) PASS -ok apigo.cc/go/js 0.572s +ok apigo.cc/go/js 0.800s ``` ## Features Verified @@ -61,4 +63,7 @@ ok apigo.cc/go/js 0.572s - [x] Context cancellation interruption. - [x] Graceful shutdown. - [x] TypeScript definition generation. +- [x] Top-level global module declarations for low-code hints. +- [x] Opaque type naming stability (`GoContext`, `GoTime`, `GoURL`). +- [x] Runtime-injected `Logger` type hints with JS-safe methods only. - [x] JS VM call stack parsing & Go dynamic call stack restoration with `jsmod.MakeError`. diff --git a/doc.go b/doc.go index 76823dd..7776f4e 100644 --- a/doc.go +++ b/doc.go @@ -16,17 +16,23 @@ func Doc() string { sb.WriteString("/**\n * Go/JS Low-Code Environment Type Definitions\n") sb.WriteString(" * Generated by js.Doc(). DO NOT EDIT.\n */\n\n") - // 1. Basic Opaque types (Manual fallback for critical non-project types) + // 1. Basic opaque types for critical non-project types sb.WriteString("/** Opaque handle to Go context.Context */\n") sb.WriteString("interface GoContext { _isGoContext: true; }\n") - sb.WriteString("/** Opaque handle to Go log.Logger */\n") - sb.WriteString("interface GoLogger { _isGoLogger: true; }\n") + sb.WriteString("/** Logger injected by the Go runtime */\n") + sb.WriteString("interface Logger {\n") + sb.WriteString(" _isLogger: true;\n") + sb.WriteString(" Debug(message: string, ...extra: any[]): void;\n") + sb.WriteString(" Info(message: string, ...extra: any[]): void;\n") + sb.WriteString(" Warning(message: string, ...extra: any[]): void;\n") + sb.WriteString(" Error(message: string, ...extra: any[]): void;\n") + sb.WriteString("}\n") sb.WriteString("/** Opaque handle to Go net/http.Request */\n") - sb.WriteString("interface GoHttp_Request { _isGoHttpReq: true; }\n") + sb.WriteString("interface GoHTTPRequest { _isGoHTTPRequest: true; }\n") sb.WriteString("/** Opaque handle to Go net/http.Response */\n") - sb.WriteString("interface GoHttp_Response { _isGoHttpRes: true; }\n") + sb.WriteString("interface GoHTTPResponse { _isGoHTTPResponse: true; }\n") sb.WriteString("/** Opaque handle to Go net/url.URL */\n") - sb.WriteString("interface GoNet_URL { _isGoNetURL: true; }\n\n") + sb.WriteString("interface GoURL { _isGoURL: true; }\n\n") modules := jsmod.GetModules() modNames := make([]string, 0, len(modules)) @@ -106,13 +112,11 @@ func Doc() string { sb.WriteString("\n\n") } - // 5. Global 'go' declaration - sb.WriteString("/** Global entry point for Go bridged modules */\n") - sb.WriteString("declare const go: {\n") + // 5. Global module declarations + sb.WriteString("/** Global Go bridged modules */\n") for _, modName := range modNames { - sb.WriteString(fmt.Sprintf(" readonly %s: %s_Module;\n", modName, strings.Title(modName))) + sb.WriteString(fmt.Sprintf("declare const %s: %s_Module;\n", modName, strings.Title(modName))) } - sb.WriteString("};\n") return sb.String() } @@ -232,6 +236,21 @@ func goTypeToTS(t reflect.Type, ctx *docCtx) string { } // 3. Special Mappings for remaining named types (mostly Structs/Interfaces) + if pkgPath == "context" && rawName == "Context" { + return "GoContext" + } + if pkgPath == "apigo.cc/go/log" && rawName == "Logger" { + return "Logger" + } + if pkgPath == "net/http" && rawName == "Request" { + return "GoHTTPRequest" + } + if pkgPath == "net/http" && rawName == "Response" { + return "GoHTTPResponse" + } + if pkgPath == "net/url" && rawName == "URL" { + return "GoURL" + } if pkgPath == "time" && rawName == "Time" { return registerInterface(t, ctx) } @@ -242,7 +261,7 @@ func goTypeToTS(t reflect.Type, ctx *docCtx) string { if pkgPath != "" && !isProject { base := filepath.Base(pkgPath) - opaqueName := "Go" + strings.Title(base) + "_" + rawName + opaqueName := strings.Title(base) + "_" + rawName ctx.opaqueList[opaqueName] = true return opaqueName } @@ -274,9 +293,7 @@ func registerInterface(t reflect.Type, ctx *docCtx) string { parts := strings.Split(pkgPath, "/") name = fmt.Sprintf("%s_%s", strings.Title(parts[len(parts)-1]), rawName) } else if pkgPath != "" { - // Standard lib types like time.Time - base := filepath.Base(pkgPath) - name = fmt.Sprintf("Go%s_%s", strings.Title(base), rawName) + name = externalTypeName(pkgPath, rawName) } else { name = fmt.Sprintf("%s_%s", strings.Title(ctx.currentMod), rawName) } @@ -330,6 +347,27 @@ func registerInterface(t reflect.Type, ctx *docCtx) string { return name } +func externalTypeName(pkgPath, rawName string) string { + base := filepath.Base(pkgPath) + baseName := externalSegmentName(base) + typeName := externalSegmentName(rawName) + if baseName == typeName { + return "Go" + typeName + } + return "Go" + baseName + typeName +} + +func externalSegmentName(name string) string { + switch strings.ToLower(name) { + case "http": + return "HTTP" + case "url": + return "URL" + default: + return strings.Title(name) + } +} + func isMethodUnusable(t reflect.Type) bool { for i := 0; i < t.NumIn(); i++ { if isTypeUnusable(t.In(i)) { diff --git a/doc_test.go b/doc_test.go index c8e44e9..c1f837e 100644 --- a/doc_test.go +++ b/doc_test.go @@ -4,6 +4,7 @@ import ( "context" "strings" "testing" + "time" "apigo.cc/go/jsmod" ) @@ -13,19 +14,35 @@ func TestDocGeneration(t *testing.T) { "query": func(ctx context.Context, sql string, args []any) ([]map[string]any, error) { return nil, nil }, + "now": func() time.Time { return time.Time{} }, "version": "1.0.0", "__exportInternal": func() *struct{ Name string } { return nil }, }) doc := Doc() - if !strings.Contains(doc, "interface GoTime") { + if !strings.Contains(doc, "interface GoContext {") { + t.Error("doc should contain GoContext interface") + } + if !strings.Contains(doc, "interface Logger {") { + t.Error("doc should contain Logger interface") + } + if !strings.Contains(doc, "Info(message: string, ...extra: any[]): void;") { + t.Error("doc should contain Logger methods") + } + if strings.Contains(doc, "GetTraceId(): string;") || strings.Contains(doc, "New(traceId: string): Logger;") || strings.Contains(doc, "As(value: T, err: any): T;") { + t.Error("doc should not contain Logger internal methods") + } + if !strings.Contains(doc, "interface GoTime {") { t.Error("doc should contain GoTime interface") } if !strings.Contains(doc, "interface Db_Module") { t.Error("doc should contain Db_Module interface") } - if !strings.Contains(doc, "declare const go:") { - t.Error("doc should contain global go declaration") + if !strings.Contains(doc, "declare const db: Db_Module;") { + t.Error("doc should contain top-level module declaration") + } + if strings.Contains(doc, "declare const go:") { + t.Error("doc should not contain legacy go namespace declaration") } if strings.Contains(doc, "__exportInternal") { t.Error("doc should NOT contain __exportInternal") diff --git a/go.mod b/go.mod index 78973e0..72721dd 100644 --- a/go.mod +++ b/go.mod @@ -11,12 +11,12 @@ require ( require ( apigo.cc/go/config v1.5.3 // indirect - apigo.cc/go/encoding v1.5.4 // indirect + apigo.cc/go/encoding v1.5.5 // indirect apigo.cc/go/file v1.5.5 // indirect - apigo.cc/go/id v1.5.4 // indirect + apigo.cc/go/id v1.5.6 // indirect apigo.cc/go/rand v1.5.3 // indirect apigo.cc/go/safe v1.5.2 // indirect - apigo.cc/go/shell v1.5.3 // indirect + apigo.cc/go/shell v1.5.4 // indirect github.com/dlclark/regexp2 v1.11.4 // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect