Compare commits
48 Commits
master
...
more-types
| Author | SHA1 | Date | |
|---|---|---|---|
| ae7ebb8127 | |||
| 388e36721e | |||
| c93627c44d | |||
| 48dfe992cc | |||
| df68281137 | |||
| 07c8367abb | |||
| d61e3d7aa6 | |||
| 0e6a217c14 | |||
| e66e0c6cae | |||
| 5d4ab2c7ac | |||
| 6647365a80 | |||
| 510e0a108d | |||
| 7f0d7047e9 | |||
| 768eaca60a | |||
| 2c6429065b | |||
| 5b52490495 | |||
| 0465225e39 | |||
| 64b0c461a2 | |||
| 803ad02772 | |||
| bd0e0d8fec | |||
| 0f57e8ffcb | |||
| f1f4620de5 | |||
| c40c973b55 | |||
| d50fb45547 | |||
| a7f7c4bb29 | |||
| e7315c38ae | |||
| f24a0d706c | |||
| 6b7014a2b7 | |||
| cafc5036ef | |||
| 1b794147ec | |||
| da8401ce16 | |||
| f57463bbd7 | |||
| 77c70f6d15 | |||
| d32060fbe9 | |||
| ede31be47c | |||
| 59b61ec584 | |||
| 713d455386 | |||
| e12666c9e4 | |||
| aa58241beb | |||
| 8c4d28ed0e | |||
| 0fba01b201 | |||
| 45eae71fe3 | |||
| 10ac5dfae8 | |||
| 2759bec46c | |||
| e04edeb2ed | |||
| 3373d9a841 | |||
| 427cf77c7f | |||
| 269682e4ab |
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,4 +3,7 @@
|
||||
/go.work.sum
|
||||
node_modules
|
||||
*.js
|
||||
*.js.map
|
||||
/example/ts/dist
|
||||
/lib/ts/dist
|
||||
/TODO.md
|
||||
|
||||
70
README.md
Normal file
70
README.md
Normal file
@ -0,0 +1,70 @@
|
||||
# Readme draft
|
||||
|
||||
1. write code
|
||||
2. annotate it (see below)
|
||||
3. launch `kitcom -src path/to/source.A -dest path/to/generated/file.B`
|
||||
4. Use generated file for IPC
|
||||
|
||||
## Typescript:
|
||||
|
||||
Currently only whole classes are supported.
|
||||
|
||||
### Annotate:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* @kittenipc api
|
||||
*/
|
||||
class ClassName {}
|
||||
```
|
||||
|
||||
### Usage:
|
||||
|
||||
```typescript
|
||||
const localApi = new LocalAPI(); // LocalAPI is written by hand
|
||||
const ipc = new ChildIPC(localApi);
|
||||
const goApi = new RemoteAPI(ipc); // RemoteAPI is generated by kitcom
|
||||
await ipc.start();
|
||||
// work
|
||||
await ipc.wait();
|
||||
```
|
||||
|
||||
## Golang:
|
||||
|
||||
Currently only whole structs are supported
|
||||
|
||||
### Annotate
|
||||
|
||||
```go
|
||||
// kittenipc:api
|
||||
type StructName struct {
|
||||
}
|
||||
```
|
||||
|
||||
### Usage:
|
||||
|
||||
```go
|
||||
localApi := LocalAPI{} // LocalAPI is written by hand
|
||||
cmd := exec.Command(fmt.Sprintf("node %s", "path to compiled js"))
|
||||
ipc, err := kittenipc.NewParent(cmd, &localApi)
|
||||
remoteApi := RemoteAPI{Ipc: ipc} // RemoteAPI is generated by kitcom
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
if err := ipc.Start(); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
// work
|
||||
if err := kit.Wait(); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
```
|
||||
|
||||
LocalAPI on one side is RemoteAPI on the other side
|
||||
|
||||
## C++, Rust, Python:
|
||||
|
||||
To be done
|
||||
|
||||
# Library status
|
||||
Work in progress. No tests, no docs, code is not finished! Not everything is working yet. Code is partly crap.
|
||||
7
example/Makefile
Normal file
7
example/Makefile
Normal file
@ -0,0 +1,7 @@
|
||||
default:
|
||||
|
||||
ipc:
|
||||
@kitcom -src golang -dest ts/src/remote.ts
|
||||
@kitcom -src ts/src -dest golang/remote.go -pkg main
|
||||
|
||||
.PHONY: ipc
|
||||
@ -6,42 +6,63 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
kittenipc "efprojects.com/kitten-ipc"
|
||||
)
|
||||
|
||||
// kittenipc:api
|
||||
type IpcApi struct {
|
||||
type GoIpcApi struct {
|
||||
}
|
||||
|
||||
func (api IpcApi) Div(a int, b int) (int, error) {
|
||||
func (api GoIpcApi) Div(a int, b int) (int, error) {
|
||||
if b == 0 {
|
||||
return 0, fmt.Errorf("zero division")
|
||||
}
|
||||
return a / b, nil
|
||||
}
|
||||
|
||||
func (api GoIpcApi) XorData(data1 []byte, data2 []byte) ([]byte, error) {
|
||||
if len(data1) == 0 || len(data2) == 0 {
|
||||
return nil, fmt.Errorf("empty input data")
|
||||
}
|
||||
if len(data1) != len(data2) {
|
||||
return nil, fmt.Errorf("input data length mismatch")
|
||||
}
|
||||
result := make([]byte, len(data1))
|
||||
for i := 0; i < len(data1); i++ {
|
||||
result[i] = data1[i] ^ data2[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
api := IpcApi{}
|
||||
localApi := GoIpcApi{}
|
||||
|
||||
cmdStr := fmt.Sprintf("node %s", path.Join(cwd, "..", "ts/index.js"))
|
||||
cmd := exec.Command(cmdStr)
|
||||
cmd := exec.Command("node", path.Join(cwd, "ts/dist/index.js"))
|
||||
|
||||
kit, err := kittenipc.NewParent(cmd, &api)
|
||||
ipc, err := kittenipc.NewParent(cmd, &localApi)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
if err := kit.Start(); err != nil {
|
||||
if err := ipc.Start(); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
if err := kit.Wait(); err != nil {
|
||||
remoteApi := TsIpcApi{Ipc: ipc}
|
||||
res, err := remoteApi.Div(10, 2)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
log.Printf("call result go->ts Div = %v", res)
|
||||
|
||||
if err := ipc.Wait(1 * time.Second); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,5 +21,5 @@ func (t *TsIpcApi) Div(
|
||||
return 0, fmt.Errorf("call to TsIpcApi.Div failed: %w", err)
|
||||
}
|
||||
_ = results
|
||||
return results[0].(int), nil
|
||||
return int(results[0].(float64)), nil
|
||||
}
|
||||
1
example/ts/dist/index.js.map
vendored
1
example/ts/dist/index.js.map
vendored
@ -1 +0,0 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,YAAY,CAAC;AACpC,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAEtC;;GAEG;AACH,MAAM,QAAQ;IACV,GAAG,CAAC,CAAS,EAAE,CAAS;QACpB,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;QACxC,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,CAAC;IACjB,CAAC;CACJ;AAED,KAAK,UAAU,IAAI;IACf,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;IAChC,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACnC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAC;IAEhC,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;IAElB,OAAO,CAAC,GAAG,CAAC,QAAQ,MAAM,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IAE9C,IAAI,CAAC;QACD,MAAM,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACT,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACrB,CAAC;IAED,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;AACrB,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;IACb,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACrB,CAAC,CAAC,CAAC"}
|
||||
20
example/ts/package-lock.json
generated
20
example/ts/package-lock.json
generated
@ -7,7 +7,10 @@
|
||||
"": {
|
||||
"name": "kitten-ipc-example",
|
||||
"version": "1.0.0",
|
||||
"license": "Apache 2.0"
|
||||
"license": "Apache 2.0",
|
||||
"dependencies": {
|
||||
"@types/node": "^22.10.5"
|
||||
}
|
||||
},
|
||||
"../../../../../../opt/homebrew/lib/node_modules/list": {
|
||||
"version": "2.0.19",
|
||||
@ -45,6 +48,21 @@
|
||||
"@types/node": "^22.10.5",
|
||||
"ts-events": "^3.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz",
|
||||
"integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,5 +8,8 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "^22.10.5"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
// Code generated by kitcom. DO NOT EDIT.
|
||||
|
||||
import { ParentIPC, ChildIPC } from "kitten-ipc";
|
||||
export default class IpcApi {
|
||||
protected ipc: ParentIPC | ChildIPC;
|
||||
|
||||
constructor(ipc: ParentIPC | ChildIPC) {
|
||||
this.ipc = ipc;
|
||||
}
|
||||
|
||||
async Div(a: number, b: number): Promise<number> {
|
||||
const results = await this.ipc.call("IpcApi.Div", a, b);
|
||||
return results[0] as number;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import {ChildIPC} from 'kitten-ipc';
|
||||
import GoIpcApi from './goapi.gen.js';
|
||||
import GoIpcApi from './remote.js';
|
||||
|
||||
/**
|
||||
* @kittenipc api
|
||||
@ -7,7 +7,7 @@ import GoIpcApi from './goapi.gen.js';
|
||||
class TsIpcApi {
|
||||
Div(a: number, b: number): number {
|
||||
if (b === 0) {
|
||||
throw new Error('division by zero');
|
||||
throw new Error('zero division');
|
||||
}
|
||||
return a / b;
|
||||
}
|
||||
@ -16,17 +16,15 @@ class TsIpcApi {
|
||||
async function main() {
|
||||
const localApi = new TsIpcApi();
|
||||
const ipc = new ChildIPC(localApi);
|
||||
const goApi = new GoIpcApi(ipc);
|
||||
const remoteApi = new GoIpcApi(ipc);
|
||||
|
||||
await ipc.start();
|
||||
|
||||
console.log(`12/3=${await goApi.Div(12, 3)}`);
|
||||
console.log(`call result ts->go Div = ${await remoteApi.Div(10, 2)}`);
|
||||
|
||||
try {
|
||||
await goApi.Div(10, 0);
|
||||
} catch (e) {
|
||||
console.trace(e);
|
||||
}
|
||||
const data1 = Buffer.alloc(10, 0b10101010);
|
||||
const data2 = Buffer.alloc(10, 0b11110000);
|
||||
console.log(`call result ts->go XorData = ${(await remoteApi.XorData(data1, data2)).toString('hex')}`);
|
||||
|
||||
await ipc.wait();
|
||||
}
|
||||
|
||||
22
example/ts/src/remote.ts
Normal file
22
example/ts/src/remote.ts
Normal file
@ -0,0 +1,22 @@
|
||||
// Code generated by kitcom. DO NOT EDIT.
|
||||
|
||||
import { ParentIPC, ChildIPC } from "kitten-ipc";
|
||||
|
||||
export default class GoIpcApi {
|
||||
protected ipc: ParentIPC | ChildIPC;
|
||||
|
||||
constructor(ipc: ParentIPC | ChildIPC) {
|
||||
this.ipc = ipc;
|
||||
}
|
||||
|
||||
async Div(a: number, b: number): Promise<number> {
|
||||
const results = await this.ipc.call("GoIpcApi.Div", a, b);
|
||||
return results[0] as number;
|
||||
}
|
||||
|
||||
async XorData(data1: Buffer, data2: Buffer): Promise<Buffer> {
|
||||
const results = await this.ipc.call("GoIpcApi.XorData", data1, data2);
|
||||
results[0] = Buffer.from(results[0], "base64");
|
||||
return results[0] as Buffer;
|
||||
}
|
||||
}
|
||||
@ -3,31 +3,30 @@
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
|
||||
"module": "nodenext",
|
||||
"target": "esnext",
|
||||
// "lib": ["esnext"],
|
||||
// "types": ["node"],
|
||||
"module": "Node16",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022"],
|
||||
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
|
||||
"strict": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitOverride": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
// "noUnusedLocals": true,
|
||||
// "noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noUncheckedSideEffectImports": true,
|
||||
|
||||
"moduleDetection": "force",
|
||||
"verbatimModuleSyntax": true,
|
||||
|
||||
// "isolatedModules": true,
|
||||
// "skipLibCheck": true
|
||||
}
|
||||
"moduleDetection": "force",
|
||||
// "isolatedModules": true,
|
||||
// "skipLibCheck": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
|
||||
SHELL := /bin/bash
|
||||
tsgo_dir = ./kitcom/internal/tsgo
|
||||
tsgo_dir = ./internal/tsgo
|
||||
my_package = efprojects.com/kitten-ipc/kitcom/internal/tsgo
|
||||
|
||||
|
||||
default:
|
||||
@echo "Please read Makefile for available targets"
|
||||
|
||||
|
||||
vendor_tsgo:
|
||||
@mkdir -p $(tsgo_dir)
|
||||
@git clone --depth 1 https://github.com/microsoft/typescript-go
|
||||
@ -13,10 +15,13 @@ vendor_tsgo:
|
||||
@cp -r ./typescript-go/internal/* $(tsgo_dir)
|
||||
@rm -rf @rm -rf typescript-go
|
||||
|
||||
|
||||
remove_tsgo_tests:
|
||||
@find $(tsgo_dir) -name "*_test.go" -exec rm {} \;
|
||||
|
||||
# just for "fun"
|
||||
|
||||
# tree shaking. written in make just for "fun"
|
||||
# caution: may cause eye hazard
|
||||
remove_tsgo_unused:
|
||||
@set -e ; \
|
||||
dirs=`find $(tsgo_dir) -type d -mindepth 1 -maxdepth 1` ; \
|
||||
@ -3,7 +3,7 @@ module efprojects.com/kitten-ipc/kitcom
|
||||
go 1.25.1
|
||||
|
||||
require (
|
||||
github.com/go-json-experiment/json v0.0.0-20250910080747-cc2cfa0554c3
|
||||
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e
|
||||
golang.org/x/sync v0.17.0
|
||||
golang.org/x/text v0.29.0
|
||||
golang.org/x/text v0.30.0
|
||||
)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
github.com/go-json-experiment/json v0.0.0-20250910080747-cc2cfa0554c3 h1:02WINGfSX5w0Mn+F28UyRoSt9uvMhKguwWMlOAh6U/0=
|
||||
github.com/go-json-experiment/json v0.0.0-20250910080747-cc2cfa0554c3/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
|
||||
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU=
|
||||
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
|
||||
{{- /*gotype: efprojects.com/kitten-ipc/kitcom.goGenData*/ -}}
|
||||
|
||||
// Code generated by kitcom. DO NOT EDIT.
|
||||
|
||||
package {{.PkgName}}
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
kittenipc "efprojects.com/kitten-ipc"
|
||||
)
|
||||
|
||||
{{range $e := .Api.Endpoints}}
|
||||
|
||||
type {{.Name}} struct {
|
||||
Ipc kittenipc.Callable
|
||||
}
|
||||
|
||||
{{range $mtd := $e.Methods}}
|
||||
func ({{$e.Name | receiver}} *{{$e.Name}}) {{$mtd.Name}}(
|
||||
{{range $mtd.Params}}{{.Name}} {{.Type | typedef}}, {{end}}
|
||||
) (
|
||||
{{range $mtd.Ret}}{{.Type | typedef}}, {{end}}error,
|
||||
) {
|
||||
results, err := {{$e.Name | receiver}}.Ipc.Call("{{$e.Name}}.{{$mtd.Name}}"{{range $mtd.Params}}, {{.Name}}{{end}})
|
||||
if err != nil {
|
||||
return {{range $mtd.Ret}}{{.Type | zerovalue}}, {{end}} fmt.Errorf("call to {{$e.Name}}.{{$mtd.Name}} failed: %w", err)
|
||||
}
|
||||
_ = results
|
||||
return {{range $idx, $ret := $mtd.Ret}}results[{{$idx}}].({{$ret.Type | typedef}}), {{end}}nil
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{end}}
|
||||
@ -13,6 +13,23 @@ const (
|
||||
TArray ValType = 5
|
||||
)
|
||||
|
||||
func (v ValType) String() string {
|
||||
switch v {
|
||||
case TInt:
|
||||
return "int"
|
||||
case TString:
|
||||
return "string"
|
||||
case TBool:
|
||||
return "bool"
|
||||
case TBlob:
|
||||
return "blob"
|
||||
case TArray:
|
||||
return "array"
|
||||
default:
|
||||
panic("unreachable code")
|
||||
}
|
||||
}
|
||||
|
||||
type Val struct {
|
||||
Name string
|
||||
Type ValType
|
||||
33
kitcom/internal/common/parser.go
Normal file
33
kitcom/internal/common/parser.go
Normal file
@ -0,0 +1,33 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/api"
|
||||
)
|
||||
|
||||
type Parser struct {
|
||||
Files []string
|
||||
}
|
||||
|
||||
func (p *Parser) AddFile(path string) {
|
||||
p.Files = append(p.Files, path)
|
||||
}
|
||||
|
||||
func (p *Parser) MapFiles(parseFile func(path string) ([]api.Endpoint, error)) (*api.Api, error) {
|
||||
var apis api.Api
|
||||
|
||||
for _, f := range p.Files {
|
||||
endpoints, err := parseFile(f)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse file: %w", err)
|
||||
}
|
||||
apis.Endpoints = append(apis.Endpoints, endpoints...)
|
||||
}
|
||||
|
||||
if len(apis.Endpoints) == 0 {
|
||||
return nil, fmt.Errorf("no endpoints found")
|
||||
}
|
||||
|
||||
return &apis, nil
|
||||
}
|
||||
@ -8,11 +8,15 @@ import (
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"efprojects.com/kitten-ipc/kitcom/api"
|
||||
_ "embed"
|
||||
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/api"
|
||||
)
|
||||
|
||||
//go:embed go_gen.tmpl
|
||||
// todo: check int overflow
|
||||
// todo: check float is whole
|
||||
|
||||
//go:embed gogen.tmpl
|
||||
var templateString string
|
||||
|
||||
type goGenData struct {
|
||||
@ -46,6 +50,17 @@ func (g *GoApiGenerator) Generate(apis *api.Api, destFile string) error {
|
||||
}
|
||||
return td, nil
|
||||
},
|
||||
"convtype": func(valDef string, t api.ValType) (string, error) {
|
||||
td, ok := map[api.ValType]string{
|
||||
api.TInt: fmt.Sprintf("int(%s.(float64))", valDef),
|
||||
api.TString: fmt.Sprintf("%s.(string)", valDef),
|
||||
api.TBool: fmt.Sprintf("%s.(bool)", valDef),
|
||||
}[t]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("cannot convert type %v for val %s", t, valDef)
|
||||
}
|
||||
return td, nil
|
||||
},
|
||||
"zerovalue": func(t api.ValType) (string, error) {
|
||||
v, ok := map[api.ValType]string{
|
||||
api.TInt: "0",
|
||||
33
kitcom/internal/golang/gogen.tmpl
Normal file
33
kitcom/internal/golang/gogen.tmpl
Normal file
@ -0,0 +1,33 @@
|
||||
{{- /*gotype: efprojects.com/kitten-ipc/kitcom/internal/golang.goGenData*/ -}}
|
||||
|
||||
// Code generated by kitcom. DO NOT EDIT.
|
||||
|
||||
package {{ .PkgName }}
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
kittenipc "efprojects.com/kitten-ipc"
|
||||
)
|
||||
|
||||
{{ range $e := .Api.Endpoints }}
|
||||
|
||||
type {{ .Name }} struct {
|
||||
Ipc kittenipc.Callable
|
||||
}
|
||||
|
||||
{{ range $mtd := $e.Methods }}
|
||||
func ({{ $e.Name | receiver }} *{{ $e.Name }}) {{ $mtd.Name }}(
|
||||
{{ range $mtd.Params }}{{ .Name }} {{ .Type | typedef }}, {{ end }}
|
||||
) (
|
||||
{{ range $mtd.Ret }}{{ .Type | typedef }}, {{ end }}error,
|
||||
) {
|
||||
results, err := {{ $e.Name | receiver }}.Ipc.Call("{{ $e.Name }}.{{ $mtd.Name }}"{{ range $mtd.Params }}, {{ .Name }}{{ end }})
|
||||
if err != nil {
|
||||
return {{ range $mtd.Ret }}{{ .Type | zerovalue }}, {{ end }} fmt.Errorf("call to {{ $e.Name }}.{{ $mtd.Name }} failed: %w", err)
|
||||
}
|
||||
_ = results
|
||||
return {{ range $idx, $ret := $mtd.Ret }}{{ convtype ( printf "results[%d]" $idx ) $ret.Type }}, {{ end }}nil
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
{{ end }}
|
||||
@ -7,39 +7,21 @@ import (
|
||||
"go/token"
|
||||
"regexp"
|
||||
|
||||
"efprojects.com/kitten-ipc/kitcom/api"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/api"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/common"
|
||||
)
|
||||
|
||||
var decorComment = regexp.MustCompile(`^//\s?kittenipc:api$`)
|
||||
|
||||
type GoApiParser struct {
|
||||
files []string
|
||||
*common.Parser
|
||||
}
|
||||
|
||||
func (g *GoApiParser) AddFile(path string) {
|
||||
g.files = append(g.files, path)
|
||||
func (p *GoApiParser) Parse() (*api.Api, error) {
|
||||
return p.MapFiles(p.parseFile)
|
||||
}
|
||||
|
||||
func (g *GoApiParser) Parse() (*api.Api, error) {
|
||||
|
||||
var apis api.Api
|
||||
|
||||
for _, f := range g.files {
|
||||
endpoints, err := g.parseFile(f)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse file: %w", err)
|
||||
}
|
||||
apis.Endpoints = append(apis.Endpoints, endpoints...)
|
||||
}
|
||||
|
||||
if len(apis.Endpoints) == 0 {
|
||||
return nil, fmt.Errorf("no endpoints found")
|
||||
}
|
||||
|
||||
return &apis, nil
|
||||
}
|
||||
|
||||
func (g *GoApiParser) parseFile(sourceFile string) ([]api.Endpoint, error) {
|
||||
func (p *GoApiParser) parseFile(sourceFile string) ([]api.Endpoint, error) {
|
||||
var endpoints []api.Endpoint
|
||||
|
||||
fileSet := token.NewFileSet()
|
||||
@ -115,45 +97,36 @@ func (g *GoApiParser) parseFile(sourceFile string) ([]api.Endpoint, error) {
|
||||
if recvIdent.Name == endpoint.Name {
|
||||
var apiMethod api.Method
|
||||
apiMethod.Name = funcDecl.Name.Name
|
||||
for _, param := range funcDecl.Type.Params.List {
|
||||
var apiPar api.Val
|
||||
ident := param.Type.(*ast.Ident)
|
||||
switch ident.Name {
|
||||
case "int":
|
||||
apiPar.Type = api.TInt
|
||||
case "string":
|
||||
apiPar.Type = api.TString
|
||||
case "bool":
|
||||
apiPar.Type = api.TBool
|
||||
default:
|
||||
return nil, fmt.Errorf("parameter type %s is not supported yet", ident.Name)
|
||||
for i, param := range funcDecl.Type.Params.List {
|
||||
apiPar, err := fieldToVal(param, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse parameter %d for method %s: %w", i, apiMethod.Name, err)
|
||||
}
|
||||
if apiPar == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(param.Names) != 1 {
|
||||
return nil, fmt.Errorf("all parameters in method %s should be named", apiMethod.Name)
|
||||
}
|
||||
apiPar.Name = param.Names[0].Name
|
||||
apiMethod.Params = append(apiMethod.Params, apiPar)
|
||||
|
||||
apiMethod.Params = append(apiMethod.Params, *apiPar)
|
||||
}
|
||||
for _, ret := range funcDecl.Type.Results.List {
|
||||
var apiRet api.Val
|
||||
ident := ret.Type.(*ast.Ident)
|
||||
switch ident.Name {
|
||||
case "int":
|
||||
apiRet.Type = api.TInt
|
||||
case "string":
|
||||
apiRet.Type = api.TString
|
||||
case "bool":
|
||||
apiRet.Type = api.TBool
|
||||
case "error":
|
||||
// errors are processed other way
|
||||
continue
|
||||
default:
|
||||
return nil, fmt.Errorf("return type %s is not supported yet", ident.Name)
|
||||
for i, ret := range funcDecl.Type.Results.List {
|
||||
apiRet, err := fieldToVal(ret, true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse return value %d for method %s: %w", i, apiMethod.Name, err)
|
||||
}
|
||||
if apiRet == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(ret.Names) > 0 {
|
||||
apiRet.Name = ret.Names[0].Name
|
||||
}
|
||||
apiMethod.Ret = append(apiMethod.Ret, apiRet)
|
||||
|
||||
apiMethod.Ret = append(apiMethod.Ret, *apiRet)
|
||||
}
|
||||
endpoints[i].Methods = append(endpoints[i].Methods, apiMethod)
|
||||
}
|
||||
@ -161,3 +134,41 @@ func (g *GoApiParser) parseFile(sourceFile string) ([]api.Endpoint, error) {
|
||||
}
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
func fieldToVal(param *ast.Field, returning bool) (*api.Val, error) {
|
||||
var val api.Val
|
||||
switch paramType := param.Type.(type) {
|
||||
case *ast.Ident:
|
||||
switch paramType.Name {
|
||||
case "int":
|
||||
val.Type = api.TInt
|
||||
case "string":
|
||||
val.Type = api.TString
|
||||
case "bool":
|
||||
val.Type = api.TBool
|
||||
case "error":
|
||||
if returning {
|
||||
return nil, nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("errors are supported only as return types")
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("parameter type %s is not supported yet", paramType.Name)
|
||||
}
|
||||
case *ast.ArrayType:
|
||||
switch elementType := paramType.Elt.(type) {
|
||||
case *ast.Ident:
|
||||
switch elementType.Name {
|
||||
case "byte":
|
||||
val.Type = api.TBlob
|
||||
default:
|
||||
return nil, fmt.Errorf("parameter type %s is not supported yet", elementType.Name)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("parameter type %T is not supported yet", elementType)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("parameter type %T is not supported yet", paramType)
|
||||
}
|
||||
return &val, nil
|
||||
}
|
||||
@ -8,11 +8,12 @@ import (
|
||||
"os/exec"
|
||||
"text/template"
|
||||
|
||||
"efprojects.com/kitten-ipc/kitcom/api"
|
||||
_ "embed"
|
||||
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/api"
|
||||
)
|
||||
|
||||
//go:embed ts_gen.tmpl
|
||||
//go:embed tsgen.tmpl
|
||||
var templateString string
|
||||
|
||||
type tsGenData struct {
|
||||
@ -34,6 +35,7 @@ func (g *TypescriptApiGenerator) Generate(apis *api.Api, destFile string) error
|
||||
api.TInt: "number",
|
||||
api.TString: "string",
|
||||
api.TBool: "boolean",
|
||||
api.TBlob: "Buffer",
|
||||
}[t]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("cannot generate type %v", t)
|
||||
31
kitcom/internal/ts/tsgen.tmpl
Normal file
31
kitcom/internal/ts/tsgen.tmpl
Normal file
@ -0,0 +1,31 @@
|
||||
{{- /*gotype: efprojects.com/kitten-ipc/kitcom/internal/ts.tsGenData*/ -}}
|
||||
|
||||
// Code generated by kitcom. DO NOT EDIT.
|
||||
|
||||
import {ParentIPC, ChildIPC} from 'kitten-ipc';
|
||||
|
||||
{{ range $e := .Api.Endpoints }}
|
||||
export default class {{ $e.Name }} {
|
||||
protected ipc: ParentIPC | ChildIPC;
|
||||
|
||||
constructor(ipc: ParentIPC | ChildIPC) {
|
||||
this.ipc = ipc;
|
||||
}
|
||||
|
||||
{{ range $mtd := $e.Methods }}
|
||||
async {{ $mtd.Name }}(
|
||||
{{ range $par := $mtd.Params }}{{ $par.Name }}: {{ $par.Type | typedef }}, {{ end }}
|
||||
): Promise<{{ if len $mtd.Ret }}{{ (index $mtd.Ret 0).Type | typedef }}{{ else }}void{{ end }}> {
|
||||
const results = await this.ipc.call('{{ $e.Name }}.{{ $mtd.Name }}',
|
||||
{{ range $par := $mtd.Params }}{{ $par.Name }}, {{ end }}
|
||||
);
|
||||
|
||||
{{- if eq (index $mtd.Ret 0).Type.String "blob" -}}
|
||||
results[0] = Buffer.from(results[0], 'base64');
|
||||
{{- end -}}
|
||||
|
||||
return {{ range $i, $ret := $mtd.Ret }}{{ if $i }}, {{ end }}results[{{ $i }}] as {{ $ret.Type | typedef }}{{ end }}
|
||||
}
|
||||
{{ end }}
|
||||
}
|
||||
{{ end }}
|
||||
@ -6,44 +6,23 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"efprojects.com/kitten-ipc/kitcom/api"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/api"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/common"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/parser"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
|
||||
)
|
||||
|
||||
const TagName = "kittenipc"
|
||||
const TagComment = "api"
|
||||
|
||||
type TypescriptApiParser struct {
|
||||
files []string
|
||||
*common.Parser
|
||||
}
|
||||
|
||||
func (t *TypescriptApiParser) AddFile(path string) {
|
||||
t.files = append(t.files, path)
|
||||
func (p *TypescriptApiParser) Parse() (*api.Api, error) {
|
||||
return p.MapFiles(p.parseFile)
|
||||
}
|
||||
|
||||
func (t *TypescriptApiParser) Parse() (*api.Api, error) {
|
||||
|
||||
var apis api.Api
|
||||
|
||||
for _, f := range t.files {
|
||||
endpoints, err := t.parseFile(f)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse file: %w", err)
|
||||
}
|
||||
apis.Endpoints = append(apis.Endpoints, endpoints...)
|
||||
}
|
||||
|
||||
if len(apis.Endpoints) == 0 {
|
||||
return nil, fmt.Errorf("no endpoints found")
|
||||
}
|
||||
|
||||
return &apis, nil
|
||||
}
|
||||
|
||||
func (t *TypescriptApiParser) parseFile(sourceFilePath string) ([]api.Endpoint, error) {
|
||||
func (p *TypescriptApiParser) parseFile(sourceFilePath string) ([]api.Endpoint, error) {
|
||||
var endpoints []api.Endpoint
|
||||
|
||||
f, err := os.Open(sourceFilePath)
|
||||
@ -72,37 +51,11 @@ func (t *TypescriptApiParser) parseFile(sourceFilePath string) ([]api.Endpoint,
|
||||
}
|
||||
cls := node.AsClassDeclaration()
|
||||
|
||||
jsDocNodes := cls.JSDoc(nil)
|
||||
if len(jsDocNodes) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var isApi bool
|
||||
|
||||
outer:
|
||||
for _, jsDocNode := range jsDocNodes {
|
||||
jsDoc := jsDocNode.AsJSDoc()
|
||||
if jsDoc.Tags == nil {
|
||||
continue
|
||||
}
|
||||
for _, tag := range jsDoc.Tags.Nodes {
|
||||
if tag.TagName().Text() == TagName {
|
||||
for _, com := range tag.Comments() {
|
||||
if strings.TrimSpace(com.Text()) == TagComment {
|
||||
isApi = true
|
||||
break outer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !isApi {
|
||||
if !p.isApiNode(cls) {
|
||||
return false
|
||||
}
|
||||
|
||||
var endpoint api.Endpoint
|
||||
|
||||
endpoint.Name = cls.Name().Text()
|
||||
|
||||
for _, member := range cls.MemberList().Nodes {
|
||||
@ -121,32 +74,23 @@ func (t *TypescriptApiParser) parseFile(sourceFilePath string) ([]api.Endpoint,
|
||||
par := parNode.AsParameterDeclaration()
|
||||
var apiPar api.Val
|
||||
apiPar.Name = par.Name().Text()
|
||||
switch par.Type.Kind {
|
||||
case ast.KindNumberKeyword:
|
||||
apiPar.Type = api.TInt
|
||||
case ast.KindStringKeyword:
|
||||
apiPar.Type = api.TString
|
||||
case ast.KindBooleanKeyword:
|
||||
apiPar.Type = api.TBool
|
||||
default:
|
||||
err = fmt.Errorf("parameter type %s is not supported yet", par.Type.Kind)
|
||||
t, typeErr := p.fieldToVal(par.Type)
|
||||
if typeErr != nil {
|
||||
err = fmt.Errorf("failed to parse parameter %s: %w", apiPar.Name, typeErr)
|
||||
return false
|
||||
}
|
||||
apiPar.Type = t
|
||||
apiMethod.Params = append(apiMethod.Params, apiPar)
|
||||
}
|
||||
|
||||
if method.Type != nil {
|
||||
var apiRet api.Val
|
||||
switch method.Type.Kind {
|
||||
case ast.KindNumberKeyword:
|
||||
apiRet.Type = api.TInt
|
||||
case ast.KindStringKeyword:
|
||||
apiRet.Type = api.TString
|
||||
case ast.KindBooleanKeyword:
|
||||
apiRet.Type = api.TBool
|
||||
default:
|
||||
err = fmt.Errorf("return type %s is not supported yet", method.Type.Kind)
|
||||
t, typeErr := p.fieldToVal(method.Type)
|
||||
if typeErr != nil {
|
||||
err = fmt.Errorf("failed to parse return type: %w", typeErr)
|
||||
return false
|
||||
}
|
||||
apiRet.Type = t
|
||||
apiMethod.Ret = []api.Val{apiRet}
|
||||
}
|
||||
endpoint.Methods = append(endpoint.Methods, apiMethod)
|
||||
@ -163,3 +107,51 @@ func (t *TypescriptApiParser) parseFile(sourceFilePath string) ([]api.Endpoint,
|
||||
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
func (p *TypescriptApiParser) fieldToVal(typ *ast.TypeNode) (api.ValType, error) {
|
||||
switch typ.Kind {
|
||||
case ast.KindNumberKeyword:
|
||||
return api.TInt, nil
|
||||
case ast.KindStringKeyword:
|
||||
return api.TString, nil
|
||||
case ast.KindBooleanKeyword:
|
||||
return api.TBool, nil
|
||||
case ast.KindTypeReference:
|
||||
refNode := typ.AsTypeReferenceNode()
|
||||
ident := refNode.TypeName.AsIdentifier()
|
||||
switch ident.Text {
|
||||
case "Buffer":
|
||||
return api.TBlob, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("reference type %s is not supported yet", ident.Text)
|
||||
}
|
||||
default:
|
||||
return 0, fmt.Errorf("type %s is not supported yet", typ.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
const TagName = "kittenipc"
|
||||
const TagComment = "api"
|
||||
|
||||
func (p *TypescriptApiParser) isApiNode(cls *ast.ClassDeclaration) bool {
|
||||
jsDocNodes := cls.JSDoc(nil)
|
||||
if len(jsDocNodes) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, jsDocNode := range jsDocNodes {
|
||||
jsDoc := jsDocNode.AsJSDoc()
|
||||
if jsDoc.Tags == nil {
|
||||
continue
|
||||
}
|
||||
for _, tag := range jsDoc.Tags.Nodes {
|
||||
if tag.TagName().Text() == TagName {
|
||||
for _, com := range tag.Comments() {
|
||||
if strings.TrimSpace(com.Text()) == TagComment {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
go test fuzz v1
|
||||
string(".ts")
|
||||
string("/**@0\n * */0")
|
||||
int(99)
|
||||
int(0)
|
||||
@ -1,5 +0,0 @@
|
||||
go test fuzz v1
|
||||
string(".ts")
|
||||
string("/")
|
||||
int(99)
|
||||
int(1)
|
||||
@ -10,9 +10,10 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"efprojects.com/kitten-ipc/kitcom/api"
|
||||
"efprojects.com/kitten-ipc/kitcom/golang"
|
||||
"efprojects.com/kitten-ipc/kitcom/ts"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/api"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/common"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/golang"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/ts"
|
||||
)
|
||||
|
||||
type ApiParser interface {
|
||||
@ -27,46 +28,45 @@ type ApiGenerator interface {
|
||||
func main() {
|
||||
src := flag.String("src", "", "Source file/dir")
|
||||
dest := flag.String("dest", "", "Dest file")
|
||||
pkgName := flag.String("pkgname", "", "Package name (for go)")
|
||||
pkgName := flag.String("pkg", "", "Package name (for go)")
|
||||
flag.Parse()
|
||||
|
||||
if *src == "" || *dest == "" {
|
||||
log.Panic("source and destination must be set")
|
||||
log.Fatalln("source and destination must be set")
|
||||
}
|
||||
|
||||
srcAbs, err := filepath.Abs(*src)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
destAbs, err := filepath.Abs(*dest)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
apiParser, err := apiParserByPath(srcAbs)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
apis, err := apiParser.Parse()
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
apiGenerator, err := apiGeneratorByPath(destAbs, *pkgName)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
apis, err := apiParser.Parse()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if err := apiGenerator.Generate(apis, destAbs); err != nil {
|
||||
log.Panic(err)
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
|
||||
func apiParserByPath(src string) (ApiParser, error) {
|
||||
|
||||
s, err := os.Stat(src)
|
||||
pathFI, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stat src: %w", err)
|
||||
}
|
||||
@ -74,21 +74,21 @@ func apiParserByPath(src string) (ApiParser, error) {
|
||||
var parser ApiParser
|
||||
var ext string
|
||||
|
||||
if s.IsDir() {
|
||||
if err := filepath.Walk(src, func(curPath string, i fs.FileInfo, err error) error {
|
||||
if pathFI.IsDir() {
|
||||
if err := filepath.Walk(src, func(curPath string, fileinfo fs.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if i.IsDir() {
|
||||
if fileinfo.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
p, err := apiParserByFilePath(i.Name())
|
||||
p, err := apiParserByFilePath(fileinfo.Name())
|
||||
if err == nil {
|
||||
if parser == nil {
|
||||
parser = p
|
||||
ext = path.Ext(i.Name())
|
||||
} else if path.Ext(i.Name()) != ext {
|
||||
ext = path.Ext(fileinfo.Name())
|
||||
} else if path.Ext(fileinfo.Name()) != ext {
|
||||
return fmt.Errorf("path contain multiple supported filetypes")
|
||||
}
|
||||
parser.AddFile(curPath)
|
||||
@ -115,9 +115,13 @@ func apiParserByPath(src string) (ApiParser, error) {
|
||||
func apiParserByFilePath(src string) (ApiParser, error) {
|
||||
switch path.Ext(src) {
|
||||
case ".go":
|
||||
return &golang.GoApiParser{}, nil
|
||||
return &golang.GoApiParser{
|
||||
Parser: &common.Parser{},
|
||||
}, nil
|
||||
case ".ts":
|
||||
return &ts.TypescriptApiParser{}, nil
|
||||
return &ts.TypescriptApiParser{
|
||||
Parser: &common.Parser{},
|
||||
}, nil
|
||||
case ".js":
|
||||
return nil, fmt.Errorf("vanilla javascript is not supported and never will be")
|
||||
case "":
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
// Code generated by kitcom. DO NOT EDIT.
|
||||
|
||||
import {ParentIPC, ChildIPC} from 'kitten-ipc';
|
||||
|
||||
{{- /*gotype: efprojects.com/kitten-ipc/kitcom/ts.tsGenData*/ -}}
|
||||
{{range $e := .Api.Endpoints}}
|
||||
export default class {{$e.Name}} {
|
||||
protected ipc: ParentIPC | ChildIPC;
|
||||
|
||||
constructor(ipc: ParentIPC | ChildIPC) {
|
||||
this.ipc = ipc;
|
||||
}
|
||||
|
||||
{{range $mtd := $e.Methods}}
|
||||
async {{ $mtd.Name }}(
|
||||
{{ range $par := $mtd.Params }}{{$par.Name}}: {{$par.Type | typedef }}, {{end}}
|
||||
): Promise<{{if len $mtd.Ret}}{{(index $mtd.Ret 0).Type | typedef }}{{else}}void{{end}}> {
|
||||
const results = await this.ipc.call('{{$e.Name}}.{{$mtd.Name}}',
|
||||
{{range $par := $mtd.Params}}{{$par.Name}}, {{end}}
|
||||
);
|
||||
return {{range $i, $ret := $mtd.Ret}}{{if $i}}, {{end}}results[{{$i}}] as {{$ret.Type | typedef}}{{end}}
|
||||
}
|
||||
{{end}}
|
||||
}
|
||||
{{end}}
|
||||
@ -2,6 +2,7 @@ package golang
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
@ -39,7 +40,7 @@ type Message struct {
|
||||
Type MsgType `json:"type"`
|
||||
Id int64 `json:"id"`
|
||||
Method string `json:"method"`
|
||||
Params Vals `json:"params"`
|
||||
Args Vals `json:"args"`
|
||||
Result Vals `json:"result"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
@ -48,13 +49,18 @@ type Callable interface {
|
||||
Call(method string, params ...any) (Vals, error)
|
||||
}
|
||||
|
||||
type pendingCall struct {
|
||||
resultChan chan mo.Result[Vals]
|
||||
resultType reflect.Type
|
||||
}
|
||||
|
||||
type ipcCommon struct {
|
||||
localApis map[string]any
|
||||
socketPath string
|
||||
conn net.Conn
|
||||
errCh chan error
|
||||
nextId int64
|
||||
pendingCalls map[int64]chan mo.Result[Vals]
|
||||
pendingCalls map[int64]*pendingCall
|
||||
processingCalls atomic.Int64
|
||||
stopRequested atomic.Bool
|
||||
mu sync.Mutex
|
||||
@ -65,7 +71,9 @@ func (ipc *ipcCommon) readConn() {
|
||||
scn.Buffer(nil, maxMessageLength)
|
||||
for scn.Scan() {
|
||||
var msg Message
|
||||
if err := json.Unmarshal(scn.Bytes(), &msg); err != nil {
|
||||
msgBytes := scn.Bytes()
|
||||
//log.Println(string(msgBytes))
|
||||
if err := json.Unmarshal(msgBytes, &msg); err != nil {
|
||||
ipc.raiseErr(fmt.Errorf("unmarshal message: %w", err))
|
||||
break
|
||||
}
|
||||
@ -91,7 +99,6 @@ func (ipc *ipcCommon) sendMsg(msg Message) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal message: %w", err)
|
||||
}
|
||||
|
||||
data = append(data, '\n')
|
||||
|
||||
if _, err := ipc.conn.Write(data); err != nil {
|
||||
@ -109,51 +116,93 @@ func (ipc *ipcCommon) handleCall(msg Message) {
|
||||
ipc.processingCalls.Add(1)
|
||||
defer ipc.processingCalls.Add(-1)
|
||||
|
||||
parts := strings.Split(msg.Method, ".")
|
||||
if len(parts) != 2 {
|
||||
ipc.sendResponse(msg.Id, nil, fmt.Errorf("invalid method: %s", msg.Method))
|
||||
return
|
||||
}
|
||||
endpointName, methodName := parts[0], parts[1]
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
ipc.sendResponse(msg.Id, nil, fmt.Errorf("handle call panicked: %s", err))
|
||||
}
|
||||
}()
|
||||
|
||||
localApi, ok := ipc.localApis[endpointName]
|
||||
if !ok {
|
||||
ipc.sendResponse(msg.Id, nil, fmt.Errorf("endpoint not found: %s", endpointName))
|
||||
return
|
||||
}
|
||||
|
||||
method := reflect.ValueOf(localApi).MethodByName(methodName)
|
||||
if !method.IsValid() {
|
||||
ipc.sendResponse(msg.Id, nil, fmt.Errorf("method not found: %s", msg.Method))
|
||||
method, err := ipc.findMethod(msg.Method)
|
||||
if err != nil {
|
||||
ipc.sendResponse(msg.Id, nil, fmt.Errorf("find method: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
argsCount := method.Type().NumIn()
|
||||
if len(msg.Params) != argsCount {
|
||||
ipc.sendResponse(msg.Id, nil, fmt.Errorf("argument count mismatch: expected %d, got %d", argsCount, len(msg.Params)))
|
||||
if len(msg.Args) != argsCount {
|
||||
ipc.sendResponse(msg.Id, nil, fmt.Errorf("args count mismatch: expected %d, got %d", argsCount, len(msg.Args)))
|
||||
return
|
||||
}
|
||||
|
||||
var args []reflect.Value
|
||||
for _, param := range msg.Params {
|
||||
args = append(args, reflect.ValueOf(param))
|
||||
for i, arg := range msg.Args {
|
||||
paramType := method.Type().In(i)
|
||||
argType := reflect.TypeOf(arg)
|
||||
arg = ipc.convType(paramType, argType, arg)
|
||||
args = append(args, reflect.ValueOf(arg))
|
||||
}
|
||||
|
||||
results := method.Call(args)
|
||||
resVals := results[0 : len(results)-1]
|
||||
resErrVal := results[len(results)-1]
|
||||
allResultVals := method.Call(args)
|
||||
retResultVals := allResultVals[0 : len(allResultVals)-1]
|
||||
errResultVals := allResultVals[len(allResultVals)-1]
|
||||
|
||||
var res []any
|
||||
for _, resVal := range resVals {
|
||||
res = append(res, resVal.Interface())
|
||||
var results []any
|
||||
for _, resVal := range retResultVals {
|
||||
results = append(results, resVal.Interface())
|
||||
}
|
||||
|
||||
var resErr error
|
||||
if !resErrVal.IsNil() {
|
||||
resErr = resErrVal.Interface().(error)
|
||||
if !errResultVals.IsNil() {
|
||||
resErr = errResultVals.Interface().(error)
|
||||
}
|
||||
|
||||
ipc.sendResponse(msg.Id, res, resErr)
|
||||
ipc.sendResponse(msg.Id, results, resErr)
|
||||
}
|
||||
|
||||
func (ipc *ipcCommon) convType(needType reflect.Type, gotType reflect.Type, arg any) any {
|
||||
switch needType.Kind() {
|
||||
case reflect.Int:
|
||||
// JSON decodes any number to float64. If we need int, we should check and convert
|
||||
if gotType.Kind() == reflect.Float64 {
|
||||
floatArg := arg.(float64)
|
||||
if float64(int64(floatArg)) == floatArg && !needType.OverflowInt(int64(floatArg)) {
|
||||
arg = int(floatArg)
|
||||
}
|
||||
}
|
||||
case reflect.Slice:
|
||||
switch needType.Elem().Kind() {
|
||||
case reflect.Uint8:
|
||||
if gotType.Kind() == reflect.String {
|
||||
var err error
|
||||
arg, err = base64.StdEncoding.DecodeString(arg.(string))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("decode base64: %s", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return arg
|
||||
}
|
||||
|
||||
func (ipc *ipcCommon) findMethod(methodName string) (reflect.Value, error) {
|
||||
parts := strings.Split(methodName, ".")
|
||||
if len(parts) != 2 {
|
||||
return reflect.Value{}, fmt.Errorf("invalid method: %s", methodName)
|
||||
}
|
||||
|
||||
endpointName, methodName := parts[0], parts[1]
|
||||
|
||||
localApi, ok := ipc.localApis[endpointName]
|
||||
if !ok {
|
||||
return reflect.Value{}, fmt.Errorf("endpoint not found: %s", endpointName)
|
||||
}
|
||||
|
||||
method := reflect.ValueOf(localApi).MethodByName(methodName)
|
||||
if !method.IsValid() {
|
||||
return reflect.Value{}, fmt.Errorf("method not found: %s", methodName)
|
||||
}
|
||||
|
||||
return method, nil
|
||||
}
|
||||
|
||||
func (ipc *ipcCommon) sendResponse(id int64, result []any, err error) {
|
||||
@ -174,7 +223,7 @@ func (ipc *ipcCommon) sendResponse(id int64, result []any, err error) {
|
||||
|
||||
func (ipc *ipcCommon) handleResponse(msg Message) {
|
||||
ipc.mu.Lock()
|
||||
ch, ok := ipc.pendingCalls[msg.Id]
|
||||
call, ok := ipc.pendingCalls[msg.Id]
|
||||
if ok {
|
||||
delete(ipc.pendingCalls, msg.Id)
|
||||
}
|
||||
@ -191,8 +240,8 @@ func (ipc *ipcCommon) handleResponse(msg Message) {
|
||||
} else {
|
||||
res = mo.Err[Vals](fmt.Errorf("remote error: %s", msg.Error))
|
||||
}
|
||||
ch <- res
|
||||
close(ch)
|
||||
call.resultChan <- res
|
||||
close(call.resultChan)
|
||||
}
|
||||
|
||||
func (ipc *ipcCommon) Call(method string, params ...any) (Vals, error) {
|
||||
@ -203,15 +252,17 @@ func (ipc *ipcCommon) Call(method string, params ...any) (Vals, error) {
|
||||
ipc.mu.Lock()
|
||||
id := ipc.nextId
|
||||
ipc.nextId++
|
||||
resChan := make(chan mo.Result[Vals], 1)
|
||||
ipc.pendingCalls[id] = resChan
|
||||
call := &pendingCall{
|
||||
resultChan: make(chan mo.Result[Vals], 1),
|
||||
}
|
||||
ipc.pendingCalls[id] = call
|
||||
ipc.mu.Unlock()
|
||||
|
||||
msg := Message{
|
||||
Type: MsgCall,
|
||||
Id: id,
|
||||
Method: method,
|
||||
Params: params,
|
||||
Args: params,
|
||||
}
|
||||
|
||||
if err := ipc.sendMsg(msg); err != nil {
|
||||
@ -221,7 +272,7 @@ func (ipc *ipcCommon) Call(method string, params ...any) (Vals, error) {
|
||||
return nil, fmt.Errorf("send call: %w", err)
|
||||
}
|
||||
|
||||
result := <-resChan
|
||||
result := <-call.resultChan
|
||||
return result.Get()
|
||||
}
|
||||
|
||||
@ -237,7 +288,7 @@ func (ipc *ipcCommon) closeConn() {
|
||||
defer ipc.mu.Unlock()
|
||||
_ = ipc.conn.Close()
|
||||
for _, call := range ipc.pendingCalls {
|
||||
call <- mo.Err[Vals](fmt.Errorf("call cancelled due to ipc termination"))
|
||||
call.resultChan <- mo.Err[Vals](fmt.Errorf("call cancelled due to ipc termination"))
|
||||
}
|
||||
}
|
||||
|
||||
@ -251,7 +302,7 @@ func NewParent(cmd *exec.Cmd, localApis ...any) (*ParentIPC, error) {
|
||||
p := ParentIPC{
|
||||
ipcCommon: &ipcCommon{
|
||||
localApis: mapTypeNames(localApis),
|
||||
pendingCalls: make(map[int64]chan mo.Result[Vals]),
|
||||
pendingCalls: make(map[int64]*pendingCall),
|
||||
errCh: make(chan error, 1),
|
||||
socketPath: filepath.Join(os.TempDir(), fmt.Sprintf("kitten-ipc-%d.sock", os.Getpid())),
|
||||
},
|
||||
@ -326,34 +377,46 @@ func (p *ParentIPC) Stop() error {
|
||||
}
|
||||
p.stopRequested.Store(true)
|
||||
if err := p.cmd.Process.Signal(syscall.SIGINT); err != nil {
|
||||
return fmt.Errorf("send SIGTERM: %w", err)
|
||||
return fmt.Errorf("send SIGINT: %w", err)
|
||||
}
|
||||
return p.Wait()
|
||||
}
|
||||
|
||||
func (p *ParentIPC) Wait() error {
|
||||
func (p *ParentIPC) Wait(timeout ...time.Duration) (retErr error) {
|
||||
waitErrCh := make(chan error, 1)
|
||||
|
||||
const defaultTimeout = time.Duration(1<<63 - 1) // max duration in go
|
||||
_timeout := variadicToOption(timeout).OrElse(defaultTimeout)
|
||||
|
||||
go func() {
|
||||
waitErrCh <- p.cmd.Wait()
|
||||
}()
|
||||
|
||||
var retErr error
|
||||
select {
|
||||
case err := <-p.errCh:
|
||||
retErr = fmt.Errorf("ipc internal error: %w", err)
|
||||
case err := <-waitErrCh:
|
||||
if err != nil {
|
||||
var exitErr *exec.ExitError
|
||||
if ok := errors.As(err, &exitErr); ok {
|
||||
if !exitErr.Success() {
|
||||
ws, ok := exitErr.Sys().(syscall.WaitStatus)
|
||||
if !(ok && ws.Signaled() && ws.Signal() == syscall.SIGINT && p.stopRequested.Load()) {
|
||||
retErr = fmt.Errorf("cmd wait: %w", err)
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case err := <-p.errCh:
|
||||
retErr = mergeErr(retErr, fmt.Errorf("ipc internal error: %w", err))
|
||||
break loop
|
||||
case err := <-waitErrCh:
|
||||
if err != nil {
|
||||
var exitErr *exec.ExitError
|
||||
if ok := errors.As(err, &exitErr); ok {
|
||||
if !exitErr.Success() {
|
||||
ws, ok := exitErr.Sys().(syscall.WaitStatus)
|
||||
if !(ok && ws.Signaled() && ws.Signal() == syscall.SIGINT && p.stopRequested.Load()) {
|
||||
retErr = mergeErr(retErr, fmt.Errorf("cmd wait: %w", err))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
retErr = mergeErr(retErr, fmt.Errorf("cmd wait: %w", err))
|
||||
}
|
||||
} else {
|
||||
retErr = fmt.Errorf("cmd wait: %w", err)
|
||||
}
|
||||
break loop
|
||||
case <-time.After(_timeout):
|
||||
p.stopRequested.Store(true)
|
||||
if err := p.cmd.Process.Signal(syscall.SIGINT); err != nil {
|
||||
retErr = mergeErr(retErr, fmt.Errorf("send SIGINT: %w", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -371,7 +434,7 @@ func NewChild(localApis ...any) (*ChildIPC, error) {
|
||||
c := ChildIPC{
|
||||
ipcCommon: &ipcCommon{
|
||||
localApis: mapTypeNames(localApis),
|
||||
pendingCalls: make(map[int64]chan mo.Result[Vals]),
|
||||
pendingCalls: make(map[int64]*pendingCall),
|
||||
errCh: make(chan error, 1),
|
||||
},
|
||||
}
|
||||
@ -426,3 +489,13 @@ func mapTypeNames(types []any) map[string]any {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func variadicToOption[T any](variadic []T) mo.Option[T] {
|
||||
if len(variadic) >= 2 {
|
||||
panic("variadic param count must be 0 or 1")
|
||||
}
|
||||
if len(variadic) == 0 {
|
||||
return mo.None[T]()
|
||||
}
|
||||
return mo.Some(variadic[0])
|
||||
}
|
||||
|
||||
40
lib/golang/lib_test.go
Normal file
40
lib/golang/lib_test.go
Normal file
@ -0,0 +1,40 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewParent(t *testing.T) {
|
||||
t.Run("socket argument in command", func(t *testing.T) {
|
||||
cmd := exec.Command("/bin/sh", ipcSocketArg, "/tmp/kek")
|
||||
_, err := NewParent(cmd)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("nonexistent binary", func(t *testing.T) {
|
||||
cmd := exec.Command("/nonexistent/binary")
|
||||
p, err := NewParent(cmd)
|
||||
assert.NoError(t, err)
|
||||
assert.Error(t, p.Start())
|
||||
})
|
||||
|
||||
t.Run("connection timeout", func(t *testing.T) {
|
||||
cmd := exec.Command("../testdata/sleep15.sh")
|
||||
p, err := NewParent(cmd)
|
||||
assert.NoError(t, err)
|
||||
assert.Error(t, p.Start())
|
||||
})
|
||||
|
||||
t.Run("child finished before accepting connection", func(t *testing.T) {
|
||||
cmd := exec.Command("../testdata/sleep3.sh")
|
||||
p, err := NewParent(cmd)
|
||||
assert.NoError(t, err)
|
||||
start := time.Now()
|
||||
assert.Error(t, p.Start())
|
||||
assert.WithinDuration(t, time.Now(), start, time.Second*4)
|
||||
})
|
||||
}
|
||||
3
lib/testdata/sleep15.sh
vendored
Executable file
3
lib/testdata/sleep15.sh
vendored
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
sleep 15s
|
||||
3
lib/testdata/sleep3.sh
vendored
Executable file
3
lib/testdata/sleep3.sh
vendored
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
sleep 3s
|
||||
2
lib/ts/dist/index.d.ts
vendored
2
lib/ts/dist/index.d.ts
vendored
@ -1,2 +0,0 @@
|
||||
export { ParentIPC, ChildIPC } from './lib.js';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
1
lib/ts/dist/index.d.ts.map
vendored
1
lib/ts/dist/index.d.ts.map
vendored
@ -1 +0,0 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAE,QAAQ,EAAC,MAAM,UAAU,CAAC"}
|
||||
1
lib/ts/dist/index.js.map
vendored
1
lib/ts/dist/index.js.map
vendored
@ -1 +0,0 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAE,QAAQ,EAAC,MAAM,UAAU,CAAC"}
|
||||
60
lib/ts/dist/lib.d.ts
vendored
60
lib/ts/dist/lib.d.ts
vendored
@ -1,60 +0,0 @@
|
||||
import * as net from 'node:net';
|
||||
declare enum MsgType {
|
||||
Call = 1,
|
||||
Response = 2
|
||||
}
|
||||
type Vals = any[];
|
||||
interface CallMessage {
|
||||
type: MsgType.Call;
|
||||
id: number;
|
||||
method: string;
|
||||
params: Vals;
|
||||
}
|
||||
interface ResponseMessage {
|
||||
type: MsgType.Response;
|
||||
id: number;
|
||||
result?: Vals;
|
||||
error?: string;
|
||||
}
|
||||
type Message = CallMessage | ResponseMessage;
|
||||
interface CallResult {
|
||||
result: Vals;
|
||||
error: Error | null;
|
||||
}
|
||||
declare abstract class IPCCommon {
|
||||
protected localApis: Record<string, any>;
|
||||
protected socketPath: string;
|
||||
protected conn: net.Socket | null;
|
||||
protected nextId: number;
|
||||
protected pendingCalls: Record<number, (result: CallResult) => void>;
|
||||
protected stopRequested: boolean;
|
||||
protected processingCalls: number;
|
||||
protected onError?: (err: Error) => void;
|
||||
protected onClose?: () => void;
|
||||
protected constructor(localApis: object[], socketPath: string);
|
||||
protected readConn(): void;
|
||||
protected processMsg(msg: Message): void;
|
||||
protected sendMsg(msg: Message): void;
|
||||
protected handleCall(msg: CallMessage): Promise<void>;
|
||||
protected handleResponse(msg: ResponseMessage): void;
|
||||
stop(): void;
|
||||
call(method: string, ...params: Vals): Promise<Vals>;
|
||||
protected raiseErr(err: Error): void;
|
||||
}
|
||||
export declare class ParentIPC extends IPCCommon {
|
||||
private readonly cmdPath;
|
||||
private readonly cmdArgs;
|
||||
private cmd;
|
||||
private readonly listener;
|
||||
constructor(cmdPath: string, cmdArgs: string[], ...localApis: object[]);
|
||||
start(): Promise<void>;
|
||||
private acceptConn;
|
||||
wait(): Promise<void>;
|
||||
}
|
||||
export declare class ChildIPC extends IPCCommon {
|
||||
constructor(...localApis: object[]);
|
||||
start(): Promise<void>;
|
||||
wait(): Promise<void>;
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=lib.d.ts.map
|
||||
1
lib/ts/dist/lib.d.ts.map
vendored
1
lib/ts/dist/lib.d.ts.map
vendored
@ -1 +0,0 @@
|
||||
{"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAUhC,aAAK,OAAO;IACR,IAAI,IAAI;IACR,QAAQ,IAAI;CACf;AAED,KAAK,IAAI,GAAG,GAAG,EAAE,CAAC;AAElB,UAAU,WAAW;IACjB,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,IAAI,CAAC;CAChB;AAED,UAAU,eAAe;IACrB,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,IAAI,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,KAAK,OAAO,GAAG,WAAW,GAAG,eAAe,CAAC;AAE7C,UAAU,UAAU;IAChB,MAAM,EAAE,IAAI,CAAC;IACb,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACvB;AAED,uBAAe,SAAS;IACpB,SAAS,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACzC,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,GAAG,IAAI,CAAQ;IACzC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAK;IAC7B,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC,CAAM;IAC1E,SAAS,CAAC,aAAa,EAAE,OAAO,CAAS;IACzC,SAAS,CAAC,eAAe,EAAE,MAAM,CAAK;IACtC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;IACzC,SAAS,CAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAE/B,SAAS,aAAa,SAAS,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM;IAS7D,SAAS,CAAC,QAAQ,IAAI,IAAI;IA4B1B,SAAS,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI;IAWxC,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI;cAWrB,UAAU,CAAC,GAAG,EAAE,WAAW;IA6C3C,SAAS,CAAC,cAAc,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI;IAapD,IAAI;IAWJ,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBpD,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI;CAGvC;AAGD,qBAAa,SAAU,SAAQ,SAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAW;IACnC,OAAO,CAAC,GAAG,CAA6B;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAa;gBAE1B,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,GAAG,SAAS,EAAE,MAAM,EAAE;IAahE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAwBd,UAAU;IAmBlB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAgB9B;AAGD,qBAAa,QAAS,SAAQ,SAAS;gBACvB,GAAG,SAAS,EAAE,MAAM,EAAE;IAI5B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAUtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAa9B"}
|
||||
1
lib/ts/dist/lib.js.map
vendored
1
lib/ts/dist/lib.js.map
vendored
File diff suppressed because one or more lines are too long
38
lib/ts/package-lock.json
generated
38
lib/ts/package-lock.json
generated
@ -1,38 +0,0 @@
|
||||
{
|
||||
"name": "kitten-ipc",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "kitten-ipc",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/node": "^22.10.5",
|
||||
"ts-events": "^3.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.18.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.12.tgz",
|
||||
"integrity": "sha512-BICHQ67iqxQGFSzfCFTT7MRQ5XcBjG5aeKh5Ok38UBbPe5fxTyE+aHFxwVrGyr8GNlqFMLKD1D3P2K/1ks8tog==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-events": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/ts-events/-/ts-events-3.4.1.tgz",
|
||||
"integrity": "sha512-px05Slmyh6Bnfi7ma0YIU6cYXnisi+iL/2lhClu+s0ZkTdfPosiGp0H8aoQW7ASSXgcXYXAqujD0CcKYr5YlAw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,27 +1,28 @@
|
||||
{
|
||||
"name": "kitten-ipc",
|
||||
"description": "ipc lib",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"author": "Egor3f <ef@efprojects.com>",
|
||||
"license": "Apache 2.0",
|
||||
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/types.d.ts",
|
||||
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/types.d.ts"
|
||||
}
|
||||
},
|
||||
|
||||
"scripts": {
|
||||
"build": "tsc"
|
||||
"build": "tsc",
|
||||
"test": "vitest run --teardown-timeout=20000 --test-timeout=20000 --sequence.concurrent"
|
||||
},
|
||||
|
||||
"dependencies": {
|
||||
"@types/node": "^22.10.5",
|
||||
"ts-events": "^3.4.1"
|
||||
"@types/node": "^22.10.5"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.8"
|
||||
}
|
||||
}
|
||||
|
||||
24
lib/ts/src/asyncqueue.ts
Normal file
24
lib/ts/src/asyncqueue.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export class AsyncQueue<T> {
|
||||
private store: T[] = [];
|
||||
private collectors: ((val: T[]) => void)[] = [];
|
||||
|
||||
put(val: T) {
|
||||
this.store.push(val);
|
||||
for(const collector of this.collectors) {
|
||||
collector(this.store);
|
||||
}
|
||||
this.collectors = [];
|
||||
}
|
||||
|
||||
async collect(): Promise<T[]> {
|
||||
if(this.store.length > 0) {
|
||||
const store = this.store;
|
||||
this.store = [];
|
||||
return new Promise(resolve => resolve(store));
|
||||
} else {
|
||||
return new Promise(resolve => {
|
||||
this.collectors.push(resolve);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
14
lib/ts/src/lib.test.ts
Normal file
14
lib/ts/src/lib.test.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import {test} from 'vitest';
|
||||
import {ParentIPC} from './lib.js';
|
||||
|
||||
test('test connection timeout', async ({expect}) => {
|
||||
const parentIpc = new ParentIPC('../testdata/sleep15.sh', []);
|
||||
await parentIpc.start();
|
||||
await expect(parentIpc.wait()).rejects.toThrowError('timed out');
|
||||
});
|
||||
|
||||
test('test process stop before connection accept', async ({expect}) => {
|
||||
const parentIpc = new ParentIPC('../testdata/sleep3.sh', []);
|
||||
await parentIpc.start();
|
||||
await expect(parentIpc.wait()).rejects.toThrowError('command exited before connection established');
|
||||
});
|
||||
@ -5,9 +5,12 @@ import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import * as fs from 'node:fs';
|
||||
import * as util from 'node:util';
|
||||
import {AsyncQueue} from './asyncqueue.js';
|
||||
|
||||
const IPC_SOCKET_ARG = 'ipc-socket';
|
||||
|
||||
type JSONSerializable = string | number | boolean;
|
||||
|
||||
enum MsgType {
|
||||
Call = 1,
|
||||
Response = 2,
|
||||
@ -19,7 +22,7 @@ interface CallMessage {
|
||||
type: MsgType.Call,
|
||||
id: number,
|
||||
method: string;
|
||||
params: Vals;
|
||||
args: Vals;
|
||||
}
|
||||
|
||||
interface ResponseMessage {
|
||||
@ -44,7 +47,9 @@ abstract class IPCCommon {
|
||||
protected pendingCalls: Record<number, (result: CallResult) => void> = {};
|
||||
protected stopRequested: boolean = false;
|
||||
protected processingCalls: number = 0;
|
||||
protected onError?: (err: Error) => void;
|
||||
protected ready = false;
|
||||
|
||||
protected errorQueue = new AsyncQueue<Error>();
|
||||
protected onClose?: () => void;
|
||||
|
||||
protected constructor(localApis: object[], socketPath: string) {
|
||||
@ -82,12 +87,14 @@ abstract class IPCCommon {
|
||||
this.raiseErr(new Error(`${ e }`));
|
||||
}
|
||||
});
|
||||
|
||||
this.ready = true;
|
||||
}
|
||||
|
||||
protected processMsg(msg: Message): void {
|
||||
switch (msg.type) {
|
||||
case MsgType.Call:
|
||||
this.handleCall(msg);
|
||||
this.handleCall(msg).catch((e) => this.errorQueue.put(e));
|
||||
break;
|
||||
case MsgType.Response:
|
||||
this.handleResponse(msg);
|
||||
@ -124,18 +131,18 @@ abstract class IPCCommon {
|
||||
}
|
||||
|
||||
const argsCount = method.length;
|
||||
if (msg.params.length !== argsCount) {
|
||||
if (msg.args.length !== argsCount) {
|
||||
this.sendMsg({
|
||||
type: MsgType.Response,
|
||||
id: msg.id,
|
||||
error: `argument count mismatch: expected ${ argsCount }, got ${ msg.params.length }`
|
||||
error: `argument count mismatch: expected ${ argsCount }, got ${ msg.args.length }`
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.processingCalls++;
|
||||
let result = method.apply(endpoint, msg.params);
|
||||
let result = method.apply(endpoint, msg.args);
|
||||
if (result instanceof Promise) {
|
||||
result = await result;
|
||||
}
|
||||
@ -164,18 +171,7 @@ abstract class IPCCommon {
|
||||
callback({result: msg.result || [], error: err});
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.stopRequested) {
|
||||
throw new Error('close already requested');
|
||||
}
|
||||
if (!this.conn || this.conn.readyState === 'closed') {
|
||||
throw new Error('connection already closed');
|
||||
}
|
||||
this.stopRequested = true;
|
||||
if (this.onClose) this.onClose();
|
||||
}
|
||||
|
||||
call(method: string, ...params: Vals): Promise<Vals> {
|
||||
call(method: string, ...args: Vals): Promise<Vals> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = this.nextId++;
|
||||
|
||||
@ -187,7 +183,7 @@ abstract class IPCCommon {
|
||||
}
|
||||
};
|
||||
try {
|
||||
this.sendMsg({type: MsgType.Call, id, method, params});
|
||||
this.sendMsg({type: MsgType.Call, id, method, args: args.map(this.convType)});
|
||||
} catch (e) {
|
||||
delete this.pendingCalls[id];
|
||||
reject(new Error(`send call: ${ e }`));
|
||||
@ -195,8 +191,36 @@ abstract class IPCCommon {
|
||||
});
|
||||
}
|
||||
|
||||
private convType(arg: any): JSONSerializable {
|
||||
// noinspection FallThroughInSwitchStatementJS
|
||||
switch (typeof arg) {
|
||||
case 'string':
|
||||
case 'boolean':
|
||||
case 'number':
|
||||
return arg;
|
||||
// @ts-expect-error TS7029
|
||||
case 'object':
|
||||
if(arg instanceof Buffer) {
|
||||
return arg.toString('base64');
|
||||
}
|
||||
default:
|
||||
throw new Error(`arg type ${typeof arg} is not supported`);
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.stopRequested) {
|
||||
throw new Error('close already requested');
|
||||
}
|
||||
if (!this.conn || this.conn.readyState === 'closed') {
|
||||
throw new Error('connection already closed');
|
||||
}
|
||||
this.stopRequested = true;
|
||||
if (this.onClose) this.onClose();
|
||||
}
|
||||
|
||||
protected raiseErr(err: Error): void {
|
||||
if (this.onError) this.onError(err);
|
||||
this.errorQueue.put(err);
|
||||
}
|
||||
}
|
||||
|
||||
@ -226,7 +250,6 @@ export class ParentIPC extends IPCCommon {
|
||||
} catch {
|
||||
}
|
||||
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
this.listener.listen(this.socketPath, () => {
|
||||
resolve();
|
||||
@ -264,19 +287,27 @@ export class ParentIPC extends IPCCommon {
|
||||
}
|
||||
|
||||
async wait(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.cmd) throw new Error('Command is not started yet');
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (!this.cmd) {
|
||||
reject('Command is not started yet');
|
||||
return;
|
||||
}
|
||||
this.cmd.addListener('close', (code, signal) => {
|
||||
if (signal || code) {
|
||||
if (signal) reject(new Error(`Process exited with signal ${ signal }`));
|
||||
else reject(new Error(`Process exited with code ${ code }`));
|
||||
} else if(!this.ready) {
|
||||
reject('command exited before connection established');
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
this.onError = (err) => {
|
||||
reject(err);
|
||||
};
|
||||
const errors = await this.errorQueue.collect();
|
||||
if(errors.length === 1) {
|
||||
reject(errors[0]);
|
||||
} else if(errors.length > 1) {
|
||||
reject(new Error(errors.map(Error.toString).join(', ')));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -298,10 +329,13 @@ export class ChildIPC extends IPCCommon {
|
||||
}
|
||||
|
||||
async wait(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.onError = (err) => {
|
||||
reject(err);
|
||||
};
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const errors = await this.errorQueue.collect();
|
||||
if(errors.length === 1) {
|
||||
reject(errors[0]);
|
||||
} else if(errors.length > 1) {
|
||||
reject(new Error(errors.map(Error.toString).join(', ')));
|
||||
}
|
||||
this.onClose = () => {
|
||||
if (this.processingCalls === 0) {
|
||||
this.conn?.destroy();
|
||||
@ -338,6 +372,10 @@ function sleep(ms: number): Promise<void> {
|
||||
// throws on timeout
|
||||
function timeout<T>(prom: Promise<T>, ms: number): Promise<T> {
|
||||
return Promise.race(
|
||||
[prom, new Promise((res, reject) => setTimeout(reject, ms))]
|
||||
[
|
||||
prom,
|
||||
new Promise((res, reject) => {
|
||||
setTimeout(() => {reject(new Error('timed out'))}, ms)
|
||||
})]
|
||||
) as Promise<T>;
|
||||
}
|
||||
|
||||
585
lib/ts/yarn.lock
Normal file
585
lib/ts/yarn.lock
Normal file
@ -0,0 +1,585 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@esbuild/aix-ppc64@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz#80fcbe36130e58b7670511e888b8e88a259ed76c"
|
||||
integrity sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==
|
||||
|
||||
"@esbuild/android-arm64@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz#8aa4965f8d0a7982dc21734bf6601323a66da752"
|
||||
integrity sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==
|
||||
|
||||
"@esbuild/android-arm@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz#300712101f7f50f1d2627a162e6e09b109b6767a"
|
||||
integrity sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==
|
||||
|
||||
"@esbuild/android-x64@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz#87dfb27161202bdc958ef48bb61b09c758faee16"
|
||||
integrity sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==
|
||||
|
||||
"@esbuild/darwin-arm64@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz#79197898ec1ff745d21c071e1c7cc3c802f0c1fd"
|
||||
integrity sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==
|
||||
|
||||
"@esbuild/darwin-x64@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz#146400a8562133f45c4d2eadcf37ddd09718079e"
|
||||
integrity sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==
|
||||
|
||||
"@esbuild/freebsd-arm64@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz#1c5f9ba7206e158fd2b24c59fa2d2c8bb47ca0fe"
|
||||
integrity sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==
|
||||
|
||||
"@esbuild/freebsd-x64@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz#ea631f4a36beaac4b9279fa0fcc6ca29eaeeb2b3"
|
||||
integrity sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==
|
||||
|
||||
"@esbuild/linux-arm64@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz#e1066bce58394f1b1141deec8557a5f0a22f5977"
|
||||
integrity sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==
|
||||
|
||||
"@esbuild/linux-arm@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz#452cd66b20932d08bdc53a8b61c0e30baf4348b9"
|
||||
integrity sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==
|
||||
|
||||
"@esbuild/linux-ia32@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz#b24f8acc45bcf54192c7f2f3be1b53e6551eafe0"
|
||||
integrity sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==
|
||||
|
||||
"@esbuild/linux-loong64@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz#f9cfffa7fc8322571fbc4c8b3268caf15bd81ad0"
|
||||
integrity sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==
|
||||
|
||||
"@esbuild/linux-mips64el@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz#575a14bd74644ffab891adc7d7e60d275296f2cd"
|
||||
integrity sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==
|
||||
|
||||
"@esbuild/linux-ppc64@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz#75b99c70a95fbd5f7739d7692befe60601591869"
|
||||
integrity sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==
|
||||
|
||||
"@esbuild/linux-riscv64@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz#2e3259440321a44e79ddf7535c325057da875cd6"
|
||||
integrity sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==
|
||||
|
||||
"@esbuild/linux-s390x@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz#17676cabbfe5928da5b2a0d6df5d58cd08db2663"
|
||||
integrity sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==
|
||||
|
||||
"@esbuild/linux-x64@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz#0583775685ca82066d04c3507f09524d3cd7a306"
|
||||
integrity sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==
|
||||
|
||||
"@esbuild/netbsd-arm64@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz#f04c4049cb2e252fe96b16fed90f70746b13f4a4"
|
||||
integrity sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==
|
||||
|
||||
"@esbuild/netbsd-x64@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz#77da0d0a0d826d7c921eea3d40292548b258a076"
|
||||
integrity sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==
|
||||
|
||||
"@esbuild/openbsd-arm64@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz#6296f5867aedef28a81b22ab2009c786a952dccd"
|
||||
integrity sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==
|
||||
|
||||
"@esbuild/openbsd-x64@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz#f8d23303360e27b16cf065b23bbff43c14142679"
|
||||
integrity sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==
|
||||
|
||||
"@esbuild/openharmony-arm64@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz#49e0b768744a3924be0d7fd97dd6ce9b2923d88d"
|
||||
integrity sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==
|
||||
|
||||
"@esbuild/sunos-x64@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz#a6ed7d6778d67e528c81fb165b23f4911b9b13d6"
|
||||
integrity sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==
|
||||
|
||||
"@esbuild/win32-arm64@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz#9ac14c378e1b653af17d08e7d3ce34caef587323"
|
||||
integrity sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==
|
||||
|
||||
"@esbuild/win32-ia32@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz#918942dcbbb35cc14fca39afb91b5e6a3d127267"
|
||||
integrity sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==
|
||||
|
||||
"@esbuild/win32-x64@0.25.12":
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz#9bdad8176be7811ad148d1f8772359041f46c6c5"
|
||||
integrity sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==
|
||||
|
||||
"@jridgewell/sourcemap-codec@^1.5.5":
|
||||
version "1.5.5"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba"
|
||||
integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
|
||||
|
||||
"@rollup/rollup-android-arm-eabi@4.53.1":
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.1.tgz#63f6bdc496180079976e655473d5bea99b21f3ff"
|
||||
integrity sha512-bxZtughE4VNVJlL1RdoSE545kc4JxL7op57KKoi59/gwuU5rV6jLWFXXc8jwgFoT6vtj+ZjO+Z2C5nrY0Cl6wA==
|
||||
|
||||
"@rollup/rollup-android-arm64@4.53.1":
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.1.tgz#177f5e504d2f332edd0ddd3682f91ab72528fb60"
|
||||
integrity sha512-44a1hreb02cAAfAKmZfXVercPFaDjqXCK+iKeVOlJ9ltvnO6QqsBHgKVPTu+MJHSLLeMEUbeG2qiDYgbFPU48g==
|
||||
|
||||
"@rollup/rollup-darwin-arm64@4.53.1":
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.1.tgz#ffdbe0cc43c88a35be2821f99cdff4c7a5ee2116"
|
||||
integrity sha512-usmzIgD0rf1syoOZ2WZvy8YpXK5G1V3btm3QZddoGSa6mOgfXWkkv+642bfUUldomgrbiLQGrPryb7DXLovPWQ==
|
||||
|
||||
"@rollup/rollup-darwin-x64@4.53.1":
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.1.tgz#27a4852923010abbcd1f028c7e8bd6bf0ccbe755"
|
||||
integrity sha512-is3r/k4vig2Gt8mKtTlzzyaSQ+hd87kDxiN3uDSDwggJLUV56Umli6OoL+/YZa/KvtdrdyNfMKHzL/P4siOOmg==
|
||||
|
||||
"@rollup/rollup-freebsd-arm64@4.53.1":
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.1.tgz#a02b83018e487674ab445198786bef9b41cad9f0"
|
||||
integrity sha512-QJ1ksgp/bDJkZB4daldVmHaEQkG4r8PUXitCOC2WRmRaSaHx5RwPoI3DHVfXKwDkB+Sk6auFI/+JHacTekPRSw==
|
||||
|
||||
"@rollup/rollup-freebsd-x64@4.53.1":
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.1.tgz#fe898a4f0ff7c30f8377c3976ae76b89720c41da"
|
||||
integrity sha512-J6ma5xgAzvqsnU6a0+jgGX/gvoGokqpkx6zY4cWizRrm0ffhHDpJKQgC8dtDb3+MqfZDIqs64REbfHDMzxLMqQ==
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf@4.53.1":
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.1.tgz#be5a731a9f7bd7bc707457a768940b6107a9215e"
|
||||
integrity sha512-JzWRR41o2U3/KMNKRuZNsDUAcAVUYhsPuMlx5RUldw0E4lvSIXFUwejtYz1HJXohUmqs/M6BBJAUBzKXZVddbg==
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf@4.53.1":
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.1.tgz#30ce6548a9e3591303507c37280300edb0cd1d14"
|
||||
integrity sha512-L8kRIrnfMrEoHLHtHn+4uYA52fiLDEDyezgxZtGUTiII/yb04Krq+vk3P2Try+Vya9LeCE9ZHU8CXD6J9EhzHQ==
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu@4.53.1":
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.1.tgz#ec76f4223335e86cd61b0d596f34e97223f4f711"
|
||||
integrity sha512-ysAc0MFRV+WtQ8li8hi3EoFi7us6d1UzaS/+Dp7FYZfg3NdDljGMoVyiIp6Ucz7uhlYDBZ/zt6XI0YEZbUO11Q==
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl@4.53.1":
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.1.tgz#9d4d87c2988ec8e4bb3cf4516dda7ef6d09dcd3d"
|
||||
integrity sha512-UV6l9MJpDbDZZ/fJvqNcvO1PcivGEf1AvKuTcHoLjVZVFeAMygnamCTDikCVMRnA+qJe+B3pSbgX2+lBMqgBhA==
|
||||
|
||||
"@rollup/rollup-linux-loong64-gnu@4.53.1":
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.1.tgz#584bc6f3c33b30c3dbfdad36ac9c7792e4df5199"
|
||||
integrity sha512-UDUtelEprkA85g95Q+nj3Xf0M4hHa4DiJ+3P3h4BuGliY4NReYYqwlc0Y8ICLjN4+uIgCEvaygYlpf0hUj90Yg==
|
||||
|
||||
"@rollup/rollup-linux-ppc64-gnu@4.53.1":
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.1.tgz#3e9a3b095a7d7da6043cb9caa54439d3b598aaf5"
|
||||
integrity sha512-vrRn+BYhEtNOte/zbc2wAUQReJXxEx2URfTol6OEfY2zFEUK92pkFBSXRylDM7aHi+YqEPJt9/ABYzmcrS4SgQ==
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu@4.53.1":
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.1.tgz#f3c3d6523d246eef4aa1eed265f1ba31b9eef7c8"
|
||||
integrity sha512-gto/1CxHyi4A7YqZZNznQYrVlPSaodOBPKM+6xcDSCMVZN/Fzb4K+AIkNz/1yAYz9h3Ng+e2fY9H6bgawVq17w==
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl@4.53.1":
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.1.tgz#0a8944b4f29a1ba923fb9c2ddb829e621f004988"
|
||||
integrity sha512-KZ6Vx7jAw3aLNjFR8eYVcQVdFa/cvBzDNRFM3z7XhNNunWjA03eUrEwJYPk0G8V7Gs08IThFKcAPS4WY/ybIrQ==
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu@4.53.1":
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.1.tgz#bcb48f2d509ef6b33ba89f7d76a2f3805be8d4c8"
|
||||
integrity sha512-HvEixy2s/rWNgpwyKpXJcHmE7om1M89hxBTBi9Fs6zVuLU4gOrEMQNbNsN/tBVIMbLyysz/iwNiGtMOpLAOlvA==
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu@4.53.1":
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.1.tgz#ca9045e3b8e8dc0797e55d0229d5c664211bf366"
|
||||
integrity sha512-E/n8x2MSjAQgjj9IixO4UeEUeqXLtiA7pyoXCFYLuXpBA/t2hnbIdxHfA7kK9BFsYAoNU4st1rHYdldl8dTqGA==
|
||||
|
||||
"@rollup/rollup-linux-x64-musl@4.53.1":
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.1.tgz#740876db76078e37bd43cc8584ff1c7f6b382df8"
|
||||
integrity sha512-IhJ087PbLOQXCN6Ui/3FUkI9pWNZe/Z7rEIVOzMsOs1/HSAECCvSZ7PkIbkNqL/AZn6WbZvnoVZw/qwqYMo4/w==
|
||||
|
||||
"@rollup/rollup-openharmony-arm64@4.53.1":
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.1.tgz#3ff19213afe46b806fb6ec105f2664e4027e4cbc"
|
||||
integrity sha512-0++oPNgLJHBblreu0SFM7b3mAsBJBTY0Ksrmu9N6ZVrPiTkRgda52mWR7TKhHAsUb9noCjFvAw9l6ZO1yzaVbA==
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc@4.53.1":
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.1.tgz#cbba39610831747f8050a306811776534df1030d"
|
||||
integrity sha512-VJXivz61c5uVdbmitLkDlbcTk9Or43YC2QVLRkqp86QoeFSqI81bNgjhttqhKNMKnQMWnecOCm7lZz4s+WLGpQ==
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc@4.53.1":
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.1.tgz#5453c7ebba95d2bbfcc94c744c05586d587fb640"
|
||||
integrity sha512-NmZPVTUOitCXUH6erJDzTQ/jotYw4CnkMDjCYRxNHVD9bNyfrGoIse684F9okwzKCV4AIHRbUkeTBc9F2OOH5Q==
|
||||
|
||||
"@rollup/rollup-win32-x64-gnu@4.53.1":
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.1.tgz#01e1acb0dacb220d13c8992340f7bc868a564832"
|
||||
integrity sha512-2SNj7COIdAf6yliSpLdLG8BEsp5lgzRehgfkP0Av8zKfQFKku6JcvbobvHASPJu4f3BFxej5g+HuQPvqPhHvpQ==
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc@4.53.1":
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.1.tgz#56eeb602545ec03ce84633b331c2e3ece07b99c3"
|
||||
integrity sha512-rLarc1Ofcs3DHtgSzFO31pZsCh8g05R2azN1q3fF+H423Co87My0R+tazOEvYVKXSLh8C4LerMK41/K7wlklcg==
|
||||
|
||||
"@standard-schema/spec@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.0.0.tgz#f193b73dc316c4170f2e82a881da0f550d551b9c"
|
||||
integrity sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==
|
||||
|
||||
"@types/chai@^5.2.2":
|
||||
version "5.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-5.2.3.tgz#8e9cd9e1c3581fa6b341a5aed5588eb285be0b4a"
|
||||
integrity sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==
|
||||
dependencies:
|
||||
"@types/deep-eql" "*"
|
||||
assertion-error "^2.0.1"
|
||||
|
||||
"@types/deep-eql@*":
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/deep-eql/-/deep-eql-4.0.2.tgz#334311971d3a07121e7eb91b684a605e7eea9cbd"
|
||||
integrity sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==
|
||||
|
||||
"@types/estree@1.0.8", "@types/estree@^1.0.0":
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
|
||||
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
|
||||
|
||||
"@types/node@^22.10.5":
|
||||
version "22.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.19.0.tgz#849606ef3920850583a4e7ee0930987c35ad80be"
|
||||
integrity sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA==
|
||||
dependencies:
|
||||
undici-types "~6.21.0"
|
||||
|
||||
"@vitest/expect@4.0.8":
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-4.0.8.tgz#02df33fb1f99091df660a80b7113e6d2f176ee10"
|
||||
integrity sha512-Rv0eabdP/xjAHQGr8cjBm+NnLHNoL268lMDK85w2aAGLFoVKLd8QGnVon5lLtkXQCoYaNL0wg04EGnyKkkKhPA==
|
||||
dependencies:
|
||||
"@standard-schema/spec" "^1.0.0"
|
||||
"@types/chai" "^5.2.2"
|
||||
"@vitest/spy" "4.0.8"
|
||||
"@vitest/utils" "4.0.8"
|
||||
chai "^6.2.0"
|
||||
tinyrainbow "^3.0.3"
|
||||
|
||||
"@vitest/mocker@4.0.8":
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-4.0.8.tgz#8fe875716e742635beb132a5e93ef8c151b0a4ec"
|
||||
integrity sha512-9FRM3MZCedXH3+pIh+ME5Up2NBBHDq0wqwhOKkN4VnvCiKbVxddqH9mSGPZeawjd12pCOGnl+lo/ZGHt0/dQSg==
|
||||
dependencies:
|
||||
"@vitest/spy" "4.0.8"
|
||||
estree-walker "^3.0.3"
|
||||
magic-string "^0.30.21"
|
||||
|
||||
"@vitest/pretty-format@4.0.8":
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-4.0.8.tgz#752866f7dc62aa448af34404b2f9f1a4e1e6f656"
|
||||
integrity sha512-qRrjdRkINi9DaZHAimV+8ia9Gq6LeGz2CgIEmMLz3sBDYV53EsnLZbJMR1q84z1HZCMsf7s0orDgZn7ScXsZKg==
|
||||
dependencies:
|
||||
tinyrainbow "^3.0.3"
|
||||
|
||||
"@vitest/runner@4.0.8":
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-4.0.8.tgz#bfa9605eb5dc498dda8abe66d900caef31269ff6"
|
||||
integrity sha512-mdY8Sf1gsM8hKJUQfiPT3pn1n8RF4QBcJYFslgWh41JTfrK1cbqY8whpGCFzBl45LN028g0njLCYm0d7XxSaQQ==
|
||||
dependencies:
|
||||
"@vitest/utils" "4.0.8"
|
||||
pathe "^2.0.3"
|
||||
|
||||
"@vitest/snapshot@4.0.8":
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-4.0.8.tgz#3ec18bdfa96f8e383d12f156d1c73c7dcfe1fd3d"
|
||||
integrity sha512-Nar9OTU03KGiubrIOFhcfHg8FYaRaNT+bh5VUlNz8stFhCZPNrJvmZkhsr1jtaYvuefYFwK2Hwrq026u4uPWCw==
|
||||
dependencies:
|
||||
"@vitest/pretty-format" "4.0.8"
|
||||
magic-string "^0.30.21"
|
||||
pathe "^2.0.3"
|
||||
|
||||
"@vitest/spy@4.0.8":
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-4.0.8.tgz#d8f071143901bd2ee31a805b468c054e481ec615"
|
||||
integrity sha512-nvGVqUunyCgZH7kmo+Ord4WgZ7lN0sOULYXUOYuHr55dvg9YvMz3izfB189Pgp28w0vWFbEEfNc/c3VTrqrXeA==
|
||||
|
||||
"@vitest/utils@4.0.8":
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-4.0.8.tgz#00dcf405df47a64157c0edcc3832f678ab577cef"
|
||||
integrity sha512-pdk2phO5NDvEFfUTxcTP8RFYjVj/kfLSPIN5ebP2Mu9kcIMeAQTbknqcFEyBcC4z2pJlJI9aS5UQjcYfhmKAow==
|
||||
dependencies:
|
||||
"@vitest/pretty-format" "4.0.8"
|
||||
tinyrainbow "^3.0.3"
|
||||
|
||||
assertion-error@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7"
|
||||
integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==
|
||||
|
||||
chai@^6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/chai/-/chai-6.2.0.tgz#181bca6a219cddb99c3eeefb82483800ffa550ce"
|
||||
integrity sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==
|
||||
|
||||
debug@^4.4.3:
|
||||
version "4.4.3"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
|
||||
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
|
||||
dependencies:
|
||||
ms "^2.1.3"
|
||||
|
||||
es-module-lexer@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a"
|
||||
integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==
|
||||
|
||||
esbuild@^0.25.0:
|
||||
version "0.25.12"
|
||||
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.12.tgz#97a1d041f4ab00c2fce2f838d2b9969a2d2a97a5"
|
||||
integrity sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==
|
||||
optionalDependencies:
|
||||
"@esbuild/aix-ppc64" "0.25.12"
|
||||
"@esbuild/android-arm" "0.25.12"
|
||||
"@esbuild/android-arm64" "0.25.12"
|
||||
"@esbuild/android-x64" "0.25.12"
|
||||
"@esbuild/darwin-arm64" "0.25.12"
|
||||
"@esbuild/darwin-x64" "0.25.12"
|
||||
"@esbuild/freebsd-arm64" "0.25.12"
|
||||
"@esbuild/freebsd-x64" "0.25.12"
|
||||
"@esbuild/linux-arm" "0.25.12"
|
||||
"@esbuild/linux-arm64" "0.25.12"
|
||||
"@esbuild/linux-ia32" "0.25.12"
|
||||
"@esbuild/linux-loong64" "0.25.12"
|
||||
"@esbuild/linux-mips64el" "0.25.12"
|
||||
"@esbuild/linux-ppc64" "0.25.12"
|
||||
"@esbuild/linux-riscv64" "0.25.12"
|
||||
"@esbuild/linux-s390x" "0.25.12"
|
||||
"@esbuild/linux-x64" "0.25.12"
|
||||
"@esbuild/netbsd-arm64" "0.25.12"
|
||||
"@esbuild/netbsd-x64" "0.25.12"
|
||||
"@esbuild/openbsd-arm64" "0.25.12"
|
||||
"@esbuild/openbsd-x64" "0.25.12"
|
||||
"@esbuild/openharmony-arm64" "0.25.12"
|
||||
"@esbuild/sunos-x64" "0.25.12"
|
||||
"@esbuild/win32-arm64" "0.25.12"
|
||||
"@esbuild/win32-ia32" "0.25.12"
|
||||
"@esbuild/win32-x64" "0.25.12"
|
||||
|
||||
estree-walker@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d"
|
||||
integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==
|
||||
dependencies:
|
||||
"@types/estree" "^1.0.0"
|
||||
|
||||
expect-type@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.2.2.tgz#c030a329fb61184126c8447585bc75a7ec6fbff3"
|
||||
integrity sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==
|
||||
|
||||
fdir@^6.5.0:
|
||||
version "6.5.0"
|
||||
resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350"
|
||||
integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
|
||||
|
||||
fsevents@~2.3.2, fsevents@~2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
||||
|
||||
magic-string@^0.30.21:
|
||||
version "0.30.21"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91"
|
||||
integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==
|
||||
dependencies:
|
||||
"@jridgewell/sourcemap-codec" "^1.5.5"
|
||||
|
||||
ms@^2.1.3:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||
|
||||
nanoid@^3.3.11:
|
||||
version "3.3.11"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
|
||||
integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
|
||||
|
||||
pathe@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716"
|
||||
integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==
|
||||
|
||||
picocolors@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
|
||||
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
|
||||
|
||||
picomatch@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042"
|
||||
integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
|
||||
|
||||
postcss@^8.5.6:
|
||||
version "8.5.6"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c"
|
||||
integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==
|
||||
dependencies:
|
||||
nanoid "^3.3.11"
|
||||
picocolors "^1.1.1"
|
||||
source-map-js "^1.2.1"
|
||||
|
||||
rollup@^4.43.0:
|
||||
version "4.53.1"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.53.1.tgz#84d1d378584a15dedfcdcff7767a8b9d92d8d3d9"
|
||||
integrity sha512-n2I0V0lN3E9cxxMqBCT3opWOiQBzRN7UG60z/WDKqdX2zHUS/39lezBcsckZFsV6fUTSnfqI7kHf60jDAPGKug==
|
||||
dependencies:
|
||||
"@types/estree" "1.0.8"
|
||||
optionalDependencies:
|
||||
"@rollup/rollup-android-arm-eabi" "4.53.1"
|
||||
"@rollup/rollup-android-arm64" "4.53.1"
|
||||
"@rollup/rollup-darwin-arm64" "4.53.1"
|
||||
"@rollup/rollup-darwin-x64" "4.53.1"
|
||||
"@rollup/rollup-freebsd-arm64" "4.53.1"
|
||||
"@rollup/rollup-freebsd-x64" "4.53.1"
|
||||
"@rollup/rollup-linux-arm-gnueabihf" "4.53.1"
|
||||
"@rollup/rollup-linux-arm-musleabihf" "4.53.1"
|
||||
"@rollup/rollup-linux-arm64-gnu" "4.53.1"
|
||||
"@rollup/rollup-linux-arm64-musl" "4.53.1"
|
||||
"@rollup/rollup-linux-loong64-gnu" "4.53.1"
|
||||
"@rollup/rollup-linux-ppc64-gnu" "4.53.1"
|
||||
"@rollup/rollup-linux-riscv64-gnu" "4.53.1"
|
||||
"@rollup/rollup-linux-riscv64-musl" "4.53.1"
|
||||
"@rollup/rollup-linux-s390x-gnu" "4.53.1"
|
||||
"@rollup/rollup-linux-x64-gnu" "4.53.1"
|
||||
"@rollup/rollup-linux-x64-musl" "4.53.1"
|
||||
"@rollup/rollup-openharmony-arm64" "4.53.1"
|
||||
"@rollup/rollup-win32-arm64-msvc" "4.53.1"
|
||||
"@rollup/rollup-win32-ia32-msvc" "4.53.1"
|
||||
"@rollup/rollup-win32-x64-gnu" "4.53.1"
|
||||
"@rollup/rollup-win32-x64-msvc" "4.53.1"
|
||||
fsevents "~2.3.2"
|
||||
|
||||
siginfo@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30"
|
||||
integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==
|
||||
|
||||
source-map-js@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
|
||||
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
|
||||
|
||||
stackback@0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b"
|
||||
integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==
|
||||
|
||||
std-env@^3.10.0:
|
||||
version "3.10.0"
|
||||
resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.10.0.tgz#d810b27e3a073047b2b5e40034881f5ea6f9c83b"
|
||||
integrity sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==
|
||||
|
||||
tinybench@^2.9.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b"
|
||||
integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==
|
||||
|
||||
tinyexec@^0.3.2:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2"
|
||||
integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==
|
||||
|
||||
tinyglobby@^0.2.15:
|
||||
version "0.2.15"
|
||||
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2"
|
||||
integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==
|
||||
dependencies:
|
||||
fdir "^6.5.0"
|
||||
picomatch "^4.0.3"
|
||||
|
||||
tinyrainbow@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-3.0.3.tgz#984a5b1c1b25854a9b6bccbe77964d0593d1ea42"
|
||||
integrity sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==
|
||||
|
||||
typescript@^5.9.3:
|
||||
version "5.9.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"
|
||||
integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==
|
||||
|
||||
undici-types@~6.21.0:
|
||||
version "6.21.0"
|
||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb"
|
||||
integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==
|
||||
|
||||
"vite@^6.0.0 || ^7.0.0":
|
||||
version "7.2.2"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-7.2.2.tgz#17dd62eac2d0ca0fa90131c5f56e4fefb8845362"
|
||||
integrity sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==
|
||||
dependencies:
|
||||
esbuild "^0.25.0"
|
||||
fdir "^6.5.0"
|
||||
picomatch "^4.0.3"
|
||||
postcss "^8.5.6"
|
||||
rollup "^4.43.0"
|
||||
tinyglobby "^0.2.15"
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.3"
|
||||
|
||||
vitest@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/vitest/-/vitest-4.0.8.tgz#0c61a81261cf51450c70bc3c9a05a31d8526b14d"
|
||||
integrity sha512-urzu3NCEV0Qa0Y2PwvBtRgmNtxhj5t5ULw7cuKhIHh3OrkKTLlut0lnBOv9qe5OvbkMH2g38G7KPDCTpIytBVg==
|
||||
dependencies:
|
||||
"@vitest/expect" "4.0.8"
|
||||
"@vitest/mocker" "4.0.8"
|
||||
"@vitest/pretty-format" "4.0.8"
|
||||
"@vitest/runner" "4.0.8"
|
||||
"@vitest/snapshot" "4.0.8"
|
||||
"@vitest/spy" "4.0.8"
|
||||
"@vitest/utils" "4.0.8"
|
||||
debug "^4.4.3"
|
||||
es-module-lexer "^1.7.0"
|
||||
expect-type "^1.2.2"
|
||||
magic-string "^0.30.21"
|
||||
pathe "^2.0.3"
|
||||
picomatch "^4.0.3"
|
||||
std-env "^3.10.0"
|
||||
tinybench "^2.9.0"
|
||||
tinyexec "^0.3.2"
|
||||
tinyglobby "^0.2.15"
|
||||
tinyrainbow "^3.0.3"
|
||||
vite "^6.0.0 || ^7.0.0"
|
||||
why-is-node-running "^2.3.0"
|
||||
|
||||
why-is-node-running@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04"
|
||||
integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==
|
||||
dependencies:
|
||||
siginfo "^2.0.0"
|
||||
stackback "0.0.2"
|
||||
Loading…
x
Reference in New Issue
Block a user