@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 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
+ }