@teamkeel/wasm 0.240.3 → 0.240.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/encodeWasm.js +30 -0
- package/index.d.ts +53 -0
- package/index.js +65 -0
- package/index.test.ts +41 -0
- package/lib/main.go +266 -0
- package/lib/wasm_exec.js +661 -0
- package/package.json +10 -17
- package/pnpm-lock.yaml +818 -0
- package/transformKeys.js +9 -0
- package/dist/index.d.ts +0 -72
- package/dist/index.js +0 -7884
package/encodeWasm.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
|
|
3
|
+
const wasmCode = fs.readFileSync(__dirname + "/dist/keel.wasm");
|
|
4
|
+
const encoded = Buffer.from(wasmCode, "binary").toString("base64");
|
|
5
|
+
|
|
6
|
+
fs.writeFileSync(
|
|
7
|
+
__dirname + "/dist/wasm.js",
|
|
8
|
+
`
|
|
9
|
+
function asciiToBinary(str) {
|
|
10
|
+
if (typeof atob === "function") {
|
|
11
|
+
// this works in the browser
|
|
12
|
+
return atob(str);
|
|
13
|
+
} else {
|
|
14
|
+
// this works in node
|
|
15
|
+
return new Buffer(str, "base64").toString("binary");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function decode(encoded) {
|
|
20
|
+
var binaryString = asciiToBinary(encoded);
|
|
21
|
+
var bytes = new Uint8Array(binaryString.length);
|
|
22
|
+
for (var i = 0; i < binaryString.length; i++) {
|
|
23
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
24
|
+
}
|
|
25
|
+
return bytes.buffer;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports.wasm = decode("${encoded}");
|
|
29
|
+
`
|
|
30
|
+
);
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export function keel(): KeelAPI;
|
|
2
|
+
|
|
3
|
+
export interface KeelAPI {
|
|
4
|
+
format: (schemaString: string) => Promise<string>;
|
|
5
|
+
validate: (
|
|
6
|
+
schemaString: string,
|
|
7
|
+
options?: ValidateOptions
|
|
8
|
+
) => Promise<ValidationResult>;
|
|
9
|
+
completions: (
|
|
10
|
+
schemaString: string,
|
|
11
|
+
position: SimplePosition
|
|
12
|
+
) => Promise<CompletionResult>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ValidateOptions {
|
|
16
|
+
color: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SimplePosition {
|
|
20
|
+
column: number;
|
|
21
|
+
line: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface Position extends SimplePosition {
|
|
25
|
+
filename: string;
|
|
26
|
+
offset: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface CompletionItem {
|
|
30
|
+
description: string;
|
|
31
|
+
label: string;
|
|
32
|
+
insertText: string;
|
|
33
|
+
kind: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface CompletionResult {
|
|
37
|
+
completions: CompletionItem[];
|
|
38
|
+
ast: any;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ValidationError {
|
|
42
|
+
code: string;
|
|
43
|
+
pos: Position;
|
|
44
|
+
endPos: Position;
|
|
45
|
+
hint: string;
|
|
46
|
+
shortMessage: string;
|
|
47
|
+
message: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ValidationResult {
|
|
51
|
+
errors: ValidationError[];
|
|
52
|
+
ast: any;
|
|
53
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Needed for wasm_exec
|
|
2
|
+
globalThis.crypto = require("crypto");
|
|
3
|
+
|
|
4
|
+
const { transformKeys } = require("./transformKeys");
|
|
5
|
+
const { wasm } = require("./dist/wasm.js");
|
|
6
|
+
require("./lib/wasm_exec");
|
|
7
|
+
|
|
8
|
+
const instantiate = async () => {
|
|
9
|
+
const go = new globalThis.Go();
|
|
10
|
+
|
|
11
|
+
const wasmModule = await WebAssembly.instantiate(wasm, go.importObject);
|
|
12
|
+
go.run(wasmModule.instance);
|
|
13
|
+
|
|
14
|
+
return globalThis.keel;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const keel = () => {
|
|
18
|
+
const validate = async (schemaString, opts) => {
|
|
19
|
+
const api = await instantiate();
|
|
20
|
+
|
|
21
|
+
const result = api.validate(schemaString, opts || {});
|
|
22
|
+
|
|
23
|
+
if (result.error) {
|
|
24
|
+
return {
|
|
25
|
+
errors: [result.error],
|
|
26
|
+
ast: null,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!result || !result.validationErrors) {
|
|
31
|
+
return {
|
|
32
|
+
errors: [result.error],
|
|
33
|
+
ast: null,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
validationErrors: { Errors: errors },
|
|
39
|
+
ast,
|
|
40
|
+
} = result;
|
|
41
|
+
|
|
42
|
+
const transformedErrors = (errors || []).map((err) => transformKeys(err));
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
errors: transformedErrors,
|
|
46
|
+
ast: ast,
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const format = async (schemaString) => {
|
|
51
|
+
const api = await instantiate();
|
|
52
|
+
|
|
53
|
+
return api.format(schemaString);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const completions = async (schemaString, position) => {
|
|
57
|
+
const api = await instantiate();
|
|
58
|
+
|
|
59
|
+
return api.completions(schemaString, position);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return { validate, format, completions };
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
module.exports.keel = keel;
|
package/index.test.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { keel } from "./index";
|
|
2
|
+
import { test, expect } from "vitest";
|
|
3
|
+
|
|
4
|
+
test("format", async () => {
|
|
5
|
+
const api = keel();
|
|
6
|
+
const schema = `model Person { fields { name Text } }`;
|
|
7
|
+
const formatted = await api.format(schema);
|
|
8
|
+
expect(formatted).toEqual(`model Person {
|
|
9
|
+
fields {
|
|
10
|
+
name Text
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
`);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("completions", async () => {
|
|
17
|
+
const api = keel();
|
|
18
|
+
const schema = `model Person {
|
|
19
|
+
fields {
|
|
20
|
+
name Te
|
|
21
|
+
}
|
|
22
|
+
}`;
|
|
23
|
+
const { completions } = await api.completions(schema, {
|
|
24
|
+
line: 3,
|
|
25
|
+
column: 16,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
expect(completions.map((x) => x.label)).toContain("Text");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("validate", async () => {
|
|
32
|
+
const api = keel();
|
|
33
|
+
const schema = `model Person {
|
|
34
|
+
fields {
|
|
35
|
+
name Foo
|
|
36
|
+
}
|
|
37
|
+
}`;
|
|
38
|
+
const { errors } = await api.validate(schema);
|
|
39
|
+
|
|
40
|
+
expect(errors[0].message).toEqual("field name has an unsupported type Foo");
|
|
41
|
+
});
|
package/lib/main.go
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
//go:build wasm
|
|
2
|
+
|
|
3
|
+
package main
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"bytes"
|
|
7
|
+
"encoding/json"
|
|
8
|
+
"fmt"
|
|
9
|
+
"io"
|
|
10
|
+
"net/http"
|
|
11
|
+
"net/url"
|
|
12
|
+
"strings"
|
|
13
|
+
"syscall/js"
|
|
14
|
+
|
|
15
|
+
"github.com/fatih/color"
|
|
16
|
+
"github.com/graphql-go/graphql/testutil"
|
|
17
|
+
"github.com/teamkeel/keel/runtime/apis/graphql"
|
|
18
|
+
"github.com/teamkeel/keel/schema"
|
|
19
|
+
"github.com/teamkeel/keel/schema/completions"
|
|
20
|
+
"github.com/teamkeel/keel/schema/format"
|
|
21
|
+
"github.com/teamkeel/keel/schema/node"
|
|
22
|
+
"github.com/teamkeel/keel/schema/parser"
|
|
23
|
+
"github.com/teamkeel/keel/schema/reader"
|
|
24
|
+
"github.com/teamkeel/keel/schema/validation/errorhandling"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
func init() {
|
|
28
|
+
// we have to declare our functions in an init func otherwise they aren't
|
|
29
|
+
// available in JS land at the call time.
|
|
30
|
+
js.Global().Set("keel", js.ValueOf(map[string]any{
|
|
31
|
+
"validate": js.FuncOf(validate),
|
|
32
|
+
"format": js.FuncOf(formatSchema),
|
|
33
|
+
"completions": js.FuncOf(provideCompletions),
|
|
34
|
+
"getGraphQLSchemas": js.FuncOf(getGraphQLSchemas),
|
|
35
|
+
}))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func main() {
|
|
39
|
+
done := make(chan bool)
|
|
40
|
+
<-done
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// newPromise wraps the provided function in a Javascript Promise
|
|
44
|
+
// and returns that promise. It then either resolves or rejects
|
|
45
|
+
// the promise based on whether fn returns an error or not
|
|
46
|
+
func newPromise(fn func() (any, error)) any {
|
|
47
|
+
handler := js.FuncOf(func(this js.Value, args []js.Value) any {
|
|
48
|
+
resolve := args[0]
|
|
49
|
+
reject := args[1]
|
|
50
|
+
|
|
51
|
+
go func() {
|
|
52
|
+
// handle panics
|
|
53
|
+
defer func() {
|
|
54
|
+
if r := recover(); r != nil {
|
|
55
|
+
msg := "panic"
|
|
56
|
+
switch r.(type) {
|
|
57
|
+
case string:
|
|
58
|
+
msg = r.(string)
|
|
59
|
+
case error:
|
|
60
|
+
e := r.(error)
|
|
61
|
+
msg = e.Error()
|
|
62
|
+
}
|
|
63
|
+
// err should be an instance of `error`, eg `errors.New("some error")`
|
|
64
|
+
errorConstructor := js.Global().Get("Error")
|
|
65
|
+
errorObject := errorConstructor.New(msg)
|
|
66
|
+
reject.Invoke(errorObject)
|
|
67
|
+
}
|
|
68
|
+
}()
|
|
69
|
+
|
|
70
|
+
data, err := fn()
|
|
71
|
+
if err != nil {
|
|
72
|
+
// err should be an instance of `error`, eg `errors.New("some error")`
|
|
73
|
+
errorConstructor := js.Global().Get("Error")
|
|
74
|
+
errorObject := errorConstructor.New(err.Error())
|
|
75
|
+
reject.Invoke(errorObject)
|
|
76
|
+
} else {
|
|
77
|
+
resolve.Invoke(js.ValueOf(data))
|
|
78
|
+
}
|
|
79
|
+
}()
|
|
80
|
+
|
|
81
|
+
return nil
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
promiseConstructor := js.Global().Get("Promise")
|
|
85
|
+
return promiseConstructor.New(handler)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func provideCompletions(this js.Value, args []js.Value) any {
|
|
89
|
+
line := args[1].Get("line").Int()
|
|
90
|
+
column := args[1].Get("column").Int()
|
|
91
|
+
|
|
92
|
+
completions := completions.Completions(args[0].String(), &node.Position{
|
|
93
|
+
Column: column,
|
|
94
|
+
Line: line,
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
untypedCompletions := toUntypedArray(completions)
|
|
98
|
+
|
|
99
|
+
return js.ValueOf(
|
|
100
|
+
map[string]any{
|
|
101
|
+
"completions": js.ValueOf(untypedCompletions),
|
|
102
|
+
},
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// getGraphQLSchemas accepts a Keel schema string and returns
|
|
107
|
+
// a map of GraphQL schemas for each API
|
|
108
|
+
func getGraphQLSchemas(this js.Value, args []js.Value) any {
|
|
109
|
+
|
|
110
|
+
return newPromise(func() (any, error) {
|
|
111
|
+
builder := schema.Builder{}
|
|
112
|
+
|
|
113
|
+
protoSchema, err := builder.MakeFromInputs(&reader.Inputs{
|
|
114
|
+
SchemaFiles: []reader.SchemaFile{
|
|
115
|
+
{
|
|
116
|
+
FileName: "schema.keel",
|
|
117
|
+
Contents: args[0].String(),
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
})
|
|
121
|
+
if err != nil {
|
|
122
|
+
return nil, err
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
res := map[string]any{}
|
|
126
|
+
|
|
127
|
+
for _, api := range protoSchema.Apis {
|
|
128
|
+
handler := graphql.NewHandler(protoSchema, api)
|
|
129
|
+
body, err := json.Marshal(map[string]string{
|
|
130
|
+
"query": testutil.IntrospectionQuery,
|
|
131
|
+
})
|
|
132
|
+
if err != nil {
|
|
133
|
+
return nil, err
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
response := handler(&http.Request{
|
|
137
|
+
URL: &url.URL{
|
|
138
|
+
Path: "/" + strings.ToLower(api.Name) + "/graphql",
|
|
139
|
+
},
|
|
140
|
+
Method: http.MethodPost,
|
|
141
|
+
Body: io.NopCloser(bytes.NewReader(body)),
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
if response.Status != 200 {
|
|
145
|
+
return nil, fmt.Errorf("error introspecting graphql schema: %s", response.Body)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
res[api.Name] = graphql.ToGraphQLSchemaLanguage(response)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return res, nil
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
func formatSchema(this js.Value, args []js.Value) any {
|
|
156
|
+
ast, err := parser.Parse(&reader.SchemaFile{
|
|
157
|
+
FileName: "schema.keel",
|
|
158
|
+
Contents: args[0].String(),
|
|
159
|
+
})
|
|
160
|
+
if err != nil {
|
|
161
|
+
// if the schema can't be parsed then just return it as-is
|
|
162
|
+
return js.ValueOf(args[0].String())
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return js.ValueOf(format.Format(ast))
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Type definition for this function:
|
|
169
|
+
//
|
|
170
|
+
// validate(schema: string, options?: {color: boolean})
|
|
171
|
+
func validate(this js.Value, args []js.Value) any {
|
|
172
|
+
|
|
173
|
+
if len(args) > 1 {
|
|
174
|
+
withColor := args[1].Get("color").Truthy()
|
|
175
|
+
if withColor {
|
|
176
|
+
color.NoColor = false
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
schemaFile := reader.SchemaFile{
|
|
181
|
+
FileName: "schema.keel",
|
|
182
|
+
Contents: args[0].String(),
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
builder := schema.Builder{}
|
|
186
|
+
var validationErrors map[string]any
|
|
187
|
+
var validationOutput string
|
|
188
|
+
|
|
189
|
+
_, err := builder.MakeFromInputs(&reader.Inputs{
|
|
190
|
+
SchemaFiles: []reader.SchemaFile{schemaFile},
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
if err != nil {
|
|
194
|
+
errs, ok := err.(*errorhandling.ValidationErrors)
|
|
195
|
+
if ok {
|
|
196
|
+
validationErrors, err = toMap(errs)
|
|
197
|
+
if err != nil {
|
|
198
|
+
return js.ValueOf(map[string]any{
|
|
199
|
+
"error": err.Error(),
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
validationOutput, err = errs.ToAnnotatedSchema([]reader.SchemaFile{
|
|
204
|
+
schemaFile,
|
|
205
|
+
})
|
|
206
|
+
if err != nil {
|
|
207
|
+
return js.ValueOf(map[string]any{
|
|
208
|
+
"error": err.Error(),
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
return js.ValueOf(map[string]any{
|
|
213
|
+
"error": err.Error(),
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
var astMap map[string]any
|
|
219
|
+
asts := builder.ASTs()
|
|
220
|
+
if len(asts) > 0 {
|
|
221
|
+
astMap, err = toMap(asts[0])
|
|
222
|
+
if err != nil {
|
|
223
|
+
return js.ValueOf(map[string]any{
|
|
224
|
+
"error": err.Error(),
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return js.ValueOf(map[string]any{
|
|
230
|
+
"ast": astMap,
|
|
231
|
+
"validationErrors": validationErrors,
|
|
232
|
+
"validationOutput": validationOutput,
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// js.ValueOf can only marshall map[string]any to a JS object
|
|
237
|
+
// so for structs we need to do the struct->json->map[string]any dance
|
|
238
|
+
func toMap(v any) (map[string]any, error) {
|
|
239
|
+
b, err := json.Marshal(v)
|
|
240
|
+
if err != nil {
|
|
241
|
+
return nil, err
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
var res map[string]any
|
|
245
|
+
err = json.Unmarshal(b, &res)
|
|
246
|
+
return res, err
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
func toUntypedArray(items []*completions.CompletionItem) (i []any) {
|
|
250
|
+
for _, item := range items {
|
|
251
|
+
b, err := json.Marshal(item)
|
|
252
|
+
|
|
253
|
+
if err != nil {
|
|
254
|
+
continue
|
|
255
|
+
}
|
|
256
|
+
var res any
|
|
257
|
+
err = json.Unmarshal(b, &res)
|
|
258
|
+
|
|
259
|
+
if err != nil {
|
|
260
|
+
continue
|
|
261
|
+
}
|
|
262
|
+
i = append(i, res)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return i
|
|
266
|
+
}
|