graphjin 3.0.31 → 3.2.2
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/README.md +158 -284
- package/package.json +15 -13
- package/wasm/config/dev.yml +0 -128
- package/wasm/config/prod.yml +0 -21
- package/wasm/graphjin.wasm +0 -0
- package/wasm/js/globals.js +0 -12
- package/wasm/js/graphjin.js +0 -27
- package/wasm/js/install.js +0 -26
- package/wasm/js/wasm_exec.js +0 -561
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# GraphJin
|
|
1
|
+
# GraphJin - A Compiler to Connect AI to Your Databases
|
|
2
2
|
|
|
3
3
|
[](https://github.com/dosco/graphjin/blob/master/LICENSE)
|
|
4
4
|
[](https://www.npmjs.com/package/graphjin)
|
|
@@ -7,353 +7,227 @@
|
|
|
7
7
|
[](https://pkg.go.dev/github.com/dosco/graphjin/core/v3)
|
|
8
8
|
[](https://goreportcard.com/report/github.com/dosco/graphjin/core/v3)
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Point GraphJin at any database and AI assistants can query it instantly. Auto-discovers your schema, understands relationships, compiles to optimized SQL. No configuration required.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Works with PostgreSQL, MySQL, MongoDB, SQLite, Oracle, MSSQL - and models from Claude/GPT-4 to local 7B models.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
GraphJin will do auto-discovery of your database schema and relationships and generate the most efficient single SQL query to fetch all this data including a cursor to fetch the next 20 item. You don't have to do a single thing besides write the GraphQL query.
|
|
14
|
+
## Installation
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
# returns only 20 items
|
|
21
|
-
limit: 20
|
|
22
|
-
|
|
23
|
-
# orders the response items by highest price
|
|
24
|
-
order_by: { price: desc }
|
|
25
|
-
|
|
26
|
-
# only items with a price >= 20 and < 50 are returned
|
|
27
|
-
where: { price: { and: { greater_or_equals: 20, lt: 50 } } }
|
|
28
|
-
) {
|
|
29
|
-
id
|
|
30
|
-
name
|
|
31
|
-
price
|
|
32
|
-
|
|
33
|
-
# also fetch the owner of the product
|
|
34
|
-
owner {
|
|
35
|
-
full_name
|
|
36
|
-
picture: avatar
|
|
37
|
-
email
|
|
38
|
-
|
|
39
|
-
# and the categories the owner has products under
|
|
40
|
-
category_counts(limit: 3) {
|
|
41
|
-
count
|
|
42
|
-
category {
|
|
43
|
-
name
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
# and the categories of the product itself
|
|
49
|
-
category(limit: 3) {
|
|
50
|
-
id
|
|
51
|
-
name
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
# also return a cursor that we can use to fetch the next
|
|
55
|
-
# batch of products
|
|
56
|
-
products_cursor
|
|
57
|
-
}
|
|
16
|
+
**npm (all platforms)**
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g graphjin
|
|
58
19
|
```
|
|
59
20
|
|
|
60
|
-
**
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
id SERIAL NOT NULL,
|
|
65
|
-
name TEXT NOT NULL,
|
|
66
|
-
price INTEGER NOT NULL,
|
|
67
|
-
owner_id INTEGER NOT NULL,
|
|
68
|
-
category_id INTEGER NOT NULL,
|
|
69
|
-
PRIMARY KEY(id)
|
|
70
|
-
);
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## Secure out of the box
|
|
74
|
-
|
|
75
|
-
In production all queries are always read from locally saved copies not from what the client sends hence clients cannot modify the query. This makes
|
|
76
|
-
GraphJin very secure as its similar to building APIs by hand. The idea that GraphQL means that clients can change the query as they wish **does not** apply to GraphJin
|
|
21
|
+
**macOS (Homebrew)**
|
|
22
|
+
```bash
|
|
23
|
+
brew install dosco/graphjin/graphjin
|
|
24
|
+
```
|
|
77
25
|
|
|
78
|
-
|
|
26
|
+
**Windows (Scoop)**
|
|
27
|
+
```bash
|
|
28
|
+
scoop bucket add graphjin https://github.com/dosco/graphjin-scoop
|
|
29
|
+
scoop install graphjin
|
|
30
|
+
```
|
|
79
31
|
|
|
80
|
-
|
|
32
|
+
**Linux**
|
|
81
33
|
|
|
82
|
-
|
|
34
|
+
Download .deb/.rpm from [releases](https://github.com/dosco/graphjin/releases)
|
|
83
35
|
|
|
84
|
-
|
|
36
|
+
**Docker**
|
|
37
|
+
```bash
|
|
38
|
+
docker pull dosco/graphjin
|
|
39
|
+
```
|
|
85
40
|
|
|
86
|
-
##
|
|
41
|
+
## Try It Now
|
|
87
42
|
|
|
88
|
-
|
|
43
|
+
```bash
|
|
44
|
+
# With Claude Desktop - run the demo
|
|
45
|
+
graphjin mcp demo --path examples/webshop/config
|
|
46
|
+
graphjin mcp demo info # Copy output to Claude Desktop config
|
|
89
47
|
|
|
90
|
-
|
|
91
|
-
|
|
48
|
+
# With your own database
|
|
49
|
+
graphjin mcp --path /path/to/config
|
|
50
|
+
graphjin mcp info # Copy output to Claude Desktop config
|
|
92
51
|
```
|
|
93
52
|
|
|
94
|
-
|
|
95
|
-
import graphjin from "graphjin";
|
|
96
|
-
import express from "express";
|
|
97
|
-
import http from "http";
|
|
98
|
-
import pg from "pg";
|
|
99
|
-
|
|
100
|
-
const { Client } = pg;
|
|
101
|
-
const db = new Client({
|
|
102
|
-
host: "localhost",
|
|
103
|
-
port: 5432,
|
|
104
|
-
user: "postgres",
|
|
105
|
-
password: "postgres",
|
|
106
|
-
database: "appdb-development",
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
await db.connect();
|
|
110
|
-
|
|
111
|
-
// config can either be a file (eg. `dev.yml`) or an object
|
|
112
|
-
// const config = { production: true, default_limit: 50 };
|
|
113
|
-
|
|
114
|
-
var gj = await graphjin("./config", "dev.yml", db);
|
|
115
|
-
var app = express();
|
|
116
|
-
var server = http.createServer(app);
|
|
117
|
-
|
|
118
|
-
// subscriptions allow you to have a callback function triggerd
|
|
119
|
-
// automatically when data in your database changes
|
|
120
|
-
const res1 = await gj.subscribe(
|
|
121
|
-
"subscription getUpdatedUser { users(id: $userID) { id email } }",
|
|
122
|
-
null,
|
|
123
|
-
{ userID: 2 }
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
res1.data(function (res) {
|
|
127
|
-
console.log(">", res.data());
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// queries allow you to use graphql to query and update your database
|
|
131
|
-
app.get("/", async function (req, resp) {
|
|
132
|
-
const res2 = await gj.query(
|
|
133
|
-
"query getUser { users(id: $id) { id email } }",
|
|
134
|
-
{ id: 1 },
|
|
135
|
-
{ userID: 1 }
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
resp.send(res2.data());
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
server.listen(3000);
|
|
142
|
-
console.log("Express server started on port %s", server.address().port);
|
|
143
|
-
```
|
|
53
|
+
Within minutes, ask Claude: "What products do we have?" or "Show me orders from last week"
|
|
144
54
|
|
|
145
|
-
##
|
|
55
|
+
## How It Works
|
|
146
56
|
|
|
147
|
-
|
|
57
|
+
1. **Connects to database** - Reads your schema automatically
|
|
58
|
+
2. **Discovers relationships** - Foreign keys become navigable joins
|
|
59
|
+
3. **Exposes MCP tools** - Teach any LLM the query syntax
|
|
60
|
+
4. **Compiles to SQL** - Every request becomes a single optimized query
|
|
148
61
|
|
|
149
|
-
|
|
62
|
+
No resolvers. No ORM. No N+1 queries. Just point and query.
|
|
150
63
|
|
|
151
|
-
|
|
152
|
-
go get github.com/dosco/graphjin/core/v3
|
|
153
|
-
```
|
|
64
|
+
## What AI Can Do
|
|
154
65
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
"context"
|
|
160
|
-
"database/sql"
|
|
161
|
-
"log"
|
|
162
|
-
"net/http"
|
|
163
|
-
|
|
164
|
-
"github.com/dosco/graphjin/core"
|
|
165
|
-
"github.com/go-chi/chi/v5"
|
|
166
|
-
_ "github.com/jackc/pgx/v5/stdlib"
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
func main() {
|
|
170
|
-
db, err := sql.Open("pgx", "postgres://postgres:@localhost:5432/exampledb?sslmode=disable")
|
|
171
|
-
if err != nil {
|
|
172
|
-
log.Fatal(err)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
gj, err := core.NewGraphJin(nil, db)
|
|
176
|
-
if err != nil {
|
|
177
|
-
log.Fatal(err)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
query := `
|
|
181
|
-
query getPosts {
|
|
182
|
-
posts {
|
|
183
|
-
id
|
|
184
|
-
title
|
|
185
|
-
}
|
|
186
|
-
posts_cursor
|
|
187
|
-
}`
|
|
188
|
-
|
|
189
|
-
router := chi.NewRouter()
|
|
190
|
-
router.Get("/", func(w http.ResponseWriter, request *http.Request) {
|
|
191
|
-
context := context.WithValue(request.Context(), core.UserIDKey, 1)
|
|
192
|
-
res, err := gj.GraphQL(context, query, nil, nil)
|
|
193
|
-
if err != nil {
|
|
194
|
-
log.Fatal(err)
|
|
195
|
-
return
|
|
196
|
-
}
|
|
197
|
-
w.Write(res.Data)
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
log.Println("Go server started on port 3000")
|
|
201
|
-
http.ListenAndServe(":3000", router)
|
|
66
|
+
**Simple queries with filters:**
|
|
67
|
+
```graphql
|
|
68
|
+
{ products(where: { price: { gt: 50 } }, limit: 10) { id name price } }
|
|
69
|
+
```
|
|
202
70
|
|
|
71
|
+
**Nested relationships:**
|
|
72
|
+
```graphql
|
|
73
|
+
{
|
|
74
|
+
orders(limit: 5) {
|
|
75
|
+
id total
|
|
76
|
+
customer { name email }
|
|
77
|
+
items { quantity product { name category { name } } }
|
|
78
|
+
}
|
|
203
79
|
}
|
|
204
|
-
|
|
205
80
|
```
|
|
206
81
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
"github.com/dosco/graphjin/serv/v2"
|
|
212
|
-
)
|
|
82
|
+
**Aggregations:**
|
|
83
|
+
```graphql
|
|
84
|
+
{ products { count_id sum_price avg_price } }
|
|
85
|
+
```
|
|
213
86
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
87
|
+
**Mutations:**
|
|
88
|
+
```graphql
|
|
89
|
+
mutation {
|
|
90
|
+
products(insert: { name: "New Product", price: 29.99 }) { id }
|
|
217
91
|
}
|
|
92
|
+
```
|
|
218
93
|
|
|
219
|
-
|
|
220
|
-
|
|
94
|
+
**Spatial queries:**
|
|
95
|
+
```graphql
|
|
96
|
+
{
|
|
97
|
+
stores(where: { location: { st_dwithin: { point: [-122.4, 37.7], distance: 1000 } } }) {
|
|
98
|
+
name address
|
|
99
|
+
}
|
|
221
100
|
}
|
|
222
|
-
|
|
223
|
-
// if err := gj.Attach(chiRouter); err != nil {
|
|
224
|
-
// return err
|
|
225
|
-
// }
|
|
226
101
|
```
|
|
227
102
|
|
|
228
|
-
##
|
|
229
|
-
|
|
230
|
-
### Quick install
|
|
103
|
+
## Real-time Subscriptions
|
|
231
104
|
|
|
232
|
-
|
|
233
|
-
# Mac (Homebrew)
|
|
234
|
-
brew install dosco/graphjin/graphjin
|
|
105
|
+
Get live updates when your data changes. GraphJin handles thousands of concurrent subscribers with a single database query - not one per subscriber.
|
|
235
106
|
|
|
236
|
-
|
|
237
|
-
|
|
107
|
+
```graphql
|
|
108
|
+
subscription {
|
|
109
|
+
orders(where: { user_id: { eq: $user_id } }) {
|
|
110
|
+
id total status
|
|
111
|
+
items { product { name } }
|
|
112
|
+
}
|
|
113
|
+
}
|
|
238
114
|
```
|
|
239
115
|
|
|
240
|
-
|
|
241
|
-
|
|
116
|
+
**Why it's efficient:**
|
|
117
|
+
- Traditional approach: 1,000 subscribers = 1,000 database queries
|
|
118
|
+
- GraphJin: 1,000 subscribers = 1 optimized batch query
|
|
119
|
+
- Automatic change detection - updates only sent when data actually changes
|
|
120
|
+
- Built-in cursor pagination for feeds and infinite scroll
|
|
242
121
|
|
|
243
|
-
|
|
122
|
+
Works from Node.js, Go, or any WebSocket client.
|
|
244
123
|
|
|
245
|
-
|
|
246
|
-
graphjin new <app_name>
|
|
247
|
-
```
|
|
124
|
+
## MCP Tools
|
|
248
125
|
|
|
249
|
-
|
|
126
|
+
GraphJin exposes 15 tools that guide AI models to write valid queries. Key tools: `list_tables` and `describe_table` for schema discovery, `get_query_syntax` for learning the DSL, `execute_graphql` for running queries, and `execute_saved_query` for production-approved queries. Prompts like `write_query` and `fix_query_error` help models construct and debug queries.
|
|
250
127
|
|
|
251
|
-
|
|
252
|
-
# Deploy a new config
|
|
253
|
-
graphjin deploy --host=https://your-server.com --secret="your-secret-key"
|
|
128
|
+
## Database Support
|
|
254
129
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
130
|
+
| Database | Queries | Mutations | Subscriptions | Full-Text | GIS |
|
|
131
|
+
|----------|---------|-----------|---------------|-----------|-----|
|
|
132
|
+
| PostgreSQL | Yes | Yes | Yes | Yes | PostGIS |
|
|
133
|
+
| MySQL | Yes | Yes | Yes | Yes | 8.0+ |
|
|
134
|
+
| MariaDB | Yes | Yes | Yes | Yes | Yes |
|
|
135
|
+
| MSSQL | Yes | Yes | Yes | No | Yes |
|
|
136
|
+
| Oracle | Yes | Yes | Yes | No | Yes |
|
|
137
|
+
| SQLite | Yes | Yes | Yes | FTS5 | SpatiaLite |
|
|
138
|
+
| MongoDB | Yes | Yes | Yes | Yes | Yes |
|
|
139
|
+
| CockroachDB | Yes | Yes | Yes | Yes | No |
|
|
258
140
|
|
|
259
|
-
|
|
141
|
+
Also works with AWS Aurora/RDS, Google Cloud SQL, and YugabyteDB.
|
|
260
142
|
|
|
261
|
-
|
|
262
|
-
# Secure save secrets like database passwords and JWT secret keys
|
|
263
|
-
graphjin secrets
|
|
264
|
-
```
|
|
143
|
+
## Production Security
|
|
265
144
|
|
|
266
|
-
|
|
145
|
+
**Query allow-lists** - In production, only saved queries can run. AI models call `execute_saved_query` with pre-approved queries. No arbitrary SQL injection possible.
|
|
267
146
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
147
|
+
**Role-based access** - Different roles see different data:
|
|
148
|
+
```yaml
|
|
149
|
+
roles:
|
|
150
|
+
user:
|
|
151
|
+
tables:
|
|
152
|
+
- name: orders
|
|
153
|
+
query:
|
|
154
|
+
filters: ["{ user_id: { eq: $user_id } }"]
|
|
271
155
|
```
|
|
272
156
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-

|
|
276
|
-
|
|
277
|
-
## Support the Project
|
|
157
|
+
**JWT authentication** - Supports Auth0, Firebase, JWKS endpoints.
|
|
278
158
|
|
|
279
|
-
|
|
159
|
+
**Response caching** - Redis with in-memory fallback. Automatic cache invalidation.
|
|
280
160
|
|
|
281
|
-
|
|
282
|
-
<a href="https://42papers.com">
|
|
283
|
-
<img src="https://user-images.githubusercontent.com/832235/135753560-39e34be6-5734-440a-98e7-f7e160c2efb5.png" width="75" target="_blank">
|
|
284
|
-
</a>
|
|
285
|
-
<a href="https://www.exo.com.ar/">
|
|
286
|
-
<img src="https://user-images.githubusercontent.com/832235/112428182-259def80-8d11-11eb-88b8-ccef9206b535.png" width="100" target="_blank">
|
|
287
|
-
</a>
|
|
288
|
-
</div>
|
|
161
|
+
## CLI Reference
|
|
289
162
|
|
|
290
|
-
|
|
163
|
+
```bash
|
|
164
|
+
# Run MCP server (stdio mode for Claude Desktop)
|
|
165
|
+
graphjin mcp --path /path/to/config
|
|
291
166
|
|
|
292
|
-
|
|
167
|
+
# Show Claude Desktop config JSON
|
|
168
|
+
graphjin mcp info
|
|
293
169
|
|
|
294
|
-
|
|
170
|
+
# Run with temporary database container
|
|
171
|
+
graphjin mcp demo --path examples/webshop/config
|
|
295
172
|
|
|
296
|
-
|
|
173
|
+
# Show demo mode config JSON
|
|
174
|
+
graphjin mcp demo info
|
|
297
175
|
|
|
298
|
-
|
|
176
|
+
# Keep data between restarts
|
|
177
|
+
graphjin mcp demo --persist
|
|
299
178
|
|
|
300
|
-
|
|
179
|
+
# Override database type
|
|
180
|
+
graphjin mcp demo --db mysql
|
|
301
181
|
|
|
302
|
-
|
|
182
|
+
# Set auth context
|
|
183
|
+
graphjin mcp --user-id admin --user-role admin
|
|
184
|
+
```
|
|
303
185
|
|
|
304
|
-
|
|
186
|
+
**Claude Desktop config** (`claude_desktop_config.json`):
|
|
187
|
+
```json
|
|
188
|
+
{
|
|
189
|
+
"mcpServers": {
|
|
190
|
+
"my-database": {
|
|
191
|
+
"command": "graphjin",
|
|
192
|
+
"args": ["mcp", "--path", "/path/to/config"],
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
305
197
|
|
|
306
|
-
|
|
198
|
+
## Also a GraphQL API
|
|
307
199
|
|
|
308
|
-
|
|
200
|
+
GraphJin works as a traditional API too - use it from Go or as a standalone service.
|
|
309
201
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
202
|
+
### Go
|
|
203
|
+
```bash
|
|
204
|
+
go get github.com/dosco/graphjin/core/v3
|
|
205
|
+
```
|
|
206
|
+
```go
|
|
207
|
+
db, _ := sql.Open("pgx", "postgres://localhost/myapp")
|
|
208
|
+
gj, _ := core.NewGraphJin(nil, db)
|
|
209
|
+
res, _ := gj.GraphQL(ctx, `{ users { id email } }`, nil, nil)
|
|
210
|
+
```
|
|
313
211
|
|
|
314
|
-
|
|
212
|
+
### Standalone Service
|
|
213
|
+
```bash
|
|
214
|
+
brew install dosco/graphjin/graphjin # Mac
|
|
215
|
+
graphjin new myapp && cd myapp
|
|
216
|
+
graphjin serve
|
|
217
|
+
```
|
|
315
218
|
|
|
316
|
-
-
|
|
317
|
-
- Realtime updates with subscriptions
|
|
318
|
-
- Add custom business logic in Javascript
|
|
319
|
-
- Build infinite scroll, feeds, nested comments, etc
|
|
320
|
-
- Add data validations on insert or update
|
|
321
|
-
- Auto learns database tables and relationships
|
|
322
|
-
- Role and Attribute-based access control
|
|
323
|
-
- Cursor-based efficient pagination
|
|
324
|
-
- Full-text search and aggregations
|
|
325
|
-
- Automatic persisted queries
|
|
326
|
-
- JWT tokens supported (Auth0, JWKS, Firebase, etc)
|
|
327
|
-
- Join database queries with remote REST APIs
|
|
328
|
-
- Also works with existing Ruby-On-Rails apps
|
|
329
|
-
- Rails authentication supported (Redis, Memcache, Cookie)
|
|
330
|
-
- A simple config file
|
|
331
|
-
- High performance Go codebase
|
|
332
|
-
- Tiny docker image and low memory requirements
|
|
333
|
-
- Fuzz tested for security
|
|
334
|
-
- Database migrations tool
|
|
335
|
-
- Database seeding tool
|
|
336
|
-
- OpenCensus Support: Zipkin, Prometheus, X-Ray, Stackdriver
|
|
337
|
-
- API Rate Limiting
|
|
338
|
-
- Highly scalable and fast
|
|
339
|
-
- Instant Hot-deploy and rollbacks
|
|
340
|
-
- Add Custom resolvers
|
|
219
|
+
Built-in web UI at `http://localhost:8080` for query development.
|
|
341
220
|
|
|
342
221
|
## Documentation
|
|
343
222
|
|
|
344
|
-
[Quick Start](https://graphjin.com/posts/start)
|
|
345
|
-
|
|
346
|
-
[
|
|
347
|
-
|
|
348
|
-
[GraphJin GO Examples](https://pkg.go.dev/github.com/dosco/graphjin/core#pkg-examples)
|
|
349
|
-
|
|
350
|
-
## Reach out
|
|
351
|
-
|
|
352
|
-
We're happy to help you leverage GraphJin reach out if you have questions
|
|
223
|
+
- [Quick Start](https://graphjin.com/posts/start)
|
|
224
|
+
- [Full Documentation](https://graphjin.com)
|
|
225
|
+
- [Feature Reference](docs/FEATURES.md)
|
|
226
|
+
- [Go Examples](https://pkg.go.dev/github.com/dosco/graphjin/core#pkg-examples)
|
|
353
227
|
|
|
354
|
-
|
|
228
|
+
## Get in Touch
|
|
355
229
|
|
|
356
|
-
[
|
|
230
|
+
[Twitter @dosco](https://twitter.com/dosco) | [Discord](https://discord.gg/6pSWCTZ)
|
|
357
231
|
|
|
358
232
|
## License
|
|
359
233
|
|
package/package.json
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphjin",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "GraphJin - Build APIs in 5 minutes with GraphQL",
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "3.2.2",
|
|
4
|
+
"description": "GraphJin CLI - Build APIs in 5 minutes with GraphQL",
|
|
5
|
+
"bin": {
|
|
6
|
+
"graphjin": "bin/graphjin.js"
|
|
7
|
+
},
|
|
7
8
|
"files": [
|
|
8
|
-
"./
|
|
9
|
-
"./wasm/runtime",
|
|
10
|
-
"./wasm/graphjin.wasm",
|
|
11
|
-
"./wasm/js/*.js"
|
|
9
|
+
"./bin"
|
|
12
10
|
],
|
|
13
11
|
"scripts": {
|
|
14
|
-
"postinstall": "node ./
|
|
12
|
+
"postinstall": "node ./bin/install.js"
|
|
15
13
|
},
|
|
16
14
|
"repository": {
|
|
17
15
|
"type": "git",
|
|
18
|
-
"url": "https://github.com/dosco/graphjin.git"
|
|
16
|
+
"url": "git+https://github.com/dosco/graphjin.git"
|
|
19
17
|
},
|
|
20
18
|
"keywords": [
|
|
19
|
+
"graphql",
|
|
20
|
+
"sql",
|
|
21
|
+
"database",
|
|
22
|
+
"api",
|
|
23
|
+
"cli",
|
|
21
24
|
"postgres",
|
|
22
25
|
"mysql",
|
|
23
|
-
"
|
|
24
|
-
"sql"
|
|
26
|
+
"mcp"
|
|
25
27
|
],
|
|
26
28
|
"author": "Vikram <https://twitter.com/dosco>",
|
|
27
29
|
"license": "Apache-2.0",
|
|
@@ -30,6 +32,6 @@
|
|
|
30
32
|
},
|
|
31
33
|
"homepage": "https://graphjin.com",
|
|
32
34
|
"dependencies": {
|
|
33
|
-
"
|
|
35
|
+
"tar": "^7.0.0"
|
|
34
36
|
}
|
|
35
37
|
}
|
package/wasm/config/dev.yml
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
# When production mode is 'true' only queries
|
|
2
|
-
# from the allow list are permitted.
|
|
3
|
-
production: false
|
|
4
|
-
|
|
5
|
-
# Secret key for general encryption operations like
|
|
6
|
-
# encrypting the cursor data
|
|
7
|
-
secret_key: supercalifajalistics
|
|
8
|
-
|
|
9
|
-
# Subscriptions poll the database to query for updates
|
|
10
|
-
# this sets the duration (in seconds) between requests.
|
|
11
|
-
# Defaults to 5 seconds
|
|
12
|
-
subs_poll_every_seconds: 5
|
|
13
|
-
|
|
14
|
-
# Default limit value to be used on queries and as the max
|
|
15
|
-
# limit on all queries where a limit is defined as a query variable.
|
|
16
|
-
# Defaults to 20
|
|
17
|
-
default_limit: 20
|
|
18
|
-
|
|
19
|
-
# Disables all aggregation functions like count, sum, etc
|
|
20
|
-
# disable_agg_functions: false
|
|
21
|
-
|
|
22
|
-
# Disables all functions like count, length, etc
|
|
23
|
-
# disable_functions: false
|
|
24
|
-
|
|
25
|
-
# Enables using camel case terms in GraphQL which are converted
|
|
26
|
-
# to snake case in SQL
|
|
27
|
-
# enable_camelcase: false
|
|
28
|
-
|
|
29
|
-
# Set session variable "user.id" to the user id
|
|
30
|
-
# Enable this if you need the user id in triggers, etc
|
|
31
|
-
# Note: This will not work with subscriptions
|
|
32
|
-
set_user_id: false
|
|
33
|
-
|
|
34
|
-
# DefaultBlock ensures that in anonymous mode (role 'anon') all tables
|
|
35
|
-
# are blocked from queries and mutations. To open access to tables in
|
|
36
|
-
# anonymous mode they have to be added to the 'anon' role config.
|
|
37
|
-
default_block: false
|
|
38
|
-
|
|
39
|
-
# Define additional variables here to be used with filters
|
|
40
|
-
# Variables used require a type suffix eg. $user_id:bigint
|
|
41
|
-
variables:
|
|
42
|
-
#admin_account_id: "5"
|
|
43
|
-
admin_account_id: "sql:select id from users where admin = true limit 1"
|
|
44
|
-
|
|
45
|
-
# Define variables set to values extracted from http headers
|
|
46
|
-
header_variables:
|
|
47
|
-
remote_ip: "X-Forwarded-For"
|
|
48
|
-
|
|
49
|
-
# Field and table names that you wish to block
|
|
50
|
-
blocklist:
|
|
51
|
-
- ar_internal_metadata
|
|
52
|
-
- schema_migrations
|
|
53
|
-
- secret
|
|
54
|
-
- password
|
|
55
|
-
- encrypted
|
|
56
|
-
- token
|
|
57
|
-
|
|
58
|
-
# resolvers:
|
|
59
|
-
# - name: payments
|
|
60
|
-
# type: remote_api
|
|
61
|
-
# table: customers
|
|
62
|
-
# column: stripe_id
|
|
63
|
-
# json_path: data
|
|
64
|
-
# debug: false
|
|
65
|
-
# url: http://payments/payments/$id
|
|
66
|
-
# pass_headers:
|
|
67
|
-
# - cookie
|
|
68
|
-
# set_headers:
|
|
69
|
-
# - name: Host
|
|
70
|
-
# value: 0.0.0.0
|
|
71
|
-
# # - name: Authorization
|
|
72
|
-
# # value: Bearer <stripe_api_key>
|
|
73
|
-
|
|
74
|
-
tables:
|
|
75
|
-
- # You can create new fields that have a
|
|
76
|
-
# real db table backing them
|
|
77
|
-
name: me
|
|
78
|
-
table: users
|
|
79
|
-
|
|
80
|
-
- name: users
|
|
81
|
-
order_by:
|
|
82
|
-
new_users: ["created_at desc", "id asc"]
|
|
83
|
-
id: ["id asc"]
|
|
84
|
-
|
|
85
|
-
# Variables used require a type suffix eg. $user_id:bigint
|
|
86
|
-
#roles_query: "SELECT * FROM users WHERE id = $user_id:bigint"
|
|
87
|
-
|
|
88
|
-
roles:
|
|
89
|
-
# if `auth.type` is set to a valid auth type then
|
|
90
|
-
# all tables are blocked for the anon role unless
|
|
91
|
-
# added to the role like below.
|
|
92
|
-
# - name: anon
|
|
93
|
-
# tables:
|
|
94
|
-
# - name: users
|
|
95
|
-
# query:
|
|
96
|
-
# limit: 10
|
|
97
|
-
|
|
98
|
-
- name: user
|
|
99
|
-
tables:
|
|
100
|
-
- name: me
|
|
101
|
-
query:
|
|
102
|
-
filters: ["{ id: { _eq: $user_id } }"]
|
|
103
|
-
|
|
104
|
-
# - name: products
|
|
105
|
-
# query:
|
|
106
|
-
# limit: 50
|
|
107
|
-
# filters: ["{ user_id: { eq: $user_id } }"]
|
|
108
|
-
# disable_functions: false
|
|
109
|
-
|
|
110
|
-
# insert:
|
|
111
|
-
# filters: ["{ user_id: { eq: $user_id } }"]
|
|
112
|
-
# presets:
|
|
113
|
-
# - user_id: "$user_id"
|
|
114
|
-
# - created_at: "now"
|
|
115
|
-
|
|
116
|
-
# update:
|
|
117
|
-
# filters: ["{ user_id: { eq: $user_id } }"]
|
|
118
|
-
# presets:
|
|
119
|
-
# - updated_at: "now"
|
|
120
|
-
|
|
121
|
-
# delete:
|
|
122
|
-
# block: true
|
|
123
|
-
|
|
124
|
-
# - name: admin
|
|
125
|
-
# match: id = 1000
|
|
126
|
-
# tables:
|
|
127
|
-
# - name: users
|
|
128
|
-
# filters: []
|
package/wasm/config/prod.yml
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# Inherit config from this other config file
|
|
2
|
-
# so I only need to overwrite some values
|
|
3
|
-
inherits: dev
|
|
4
|
-
|
|
5
|
-
# When production mode is 'true' only queries from the queries
|
|
6
|
-
# folder can be used.
|
|
7
|
-
production: true
|
|
8
|
-
|
|
9
|
-
# Secret key for general encryption operations like
|
|
10
|
-
# encrypting the cursor data
|
|
11
|
-
secret_key: supercalifajalistics
|
|
12
|
-
|
|
13
|
-
# Subscriptions poll the database to query for updates
|
|
14
|
-
# this sets the duration (in seconds) between requests.
|
|
15
|
-
# Defaults to 5 seconds
|
|
16
|
-
subs_poll_every_seconds: 5
|
|
17
|
-
|
|
18
|
-
# Default limit value to be used on queries and as the max
|
|
19
|
-
# limit on all queries where a limit is defined as a query variable.
|
|
20
|
-
# Defaults to 20
|
|
21
|
-
default_limit: 20
|
package/wasm/graphjin.wasm
DELETED
|
Binary file
|
package/wasm/js/globals.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import crypto from "crypto"
|
|
2
|
-
|
|
3
|
-
if (typeof globalThis.crypto === 'undefined') {
|
|
4
|
-
Object.defineProperty(globalThis, 'crypto', {
|
|
5
|
-
value: {
|
|
6
|
-
getRandomValues: function(buf) { return crypto.randomFillSync(buf);}
|
|
7
|
-
},
|
|
8
|
-
enumerable: false,
|
|
9
|
-
configurable: true,
|
|
10
|
-
writable: true,
|
|
11
|
-
});
|
|
12
|
-
}
|
package/wasm/js/graphjin.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import "./globals.js"
|
|
2
|
-
import * as _ from "./wasm_exec.js";
|
|
3
|
-
|
|
4
|
-
import fs from "fs"
|
|
5
|
-
|
|
6
|
-
import { fileURLToPath } from 'url';
|
|
7
|
-
import { dirname, join } from 'path';
|
|
8
|
-
|
|
9
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
-
const __dirname = dirname(__filename);
|
|
11
|
-
|
|
12
|
-
const go = new Go();
|
|
13
|
-
const f = fs.readFileSync(join(__dirname,"../graphjin.wasm"));
|
|
14
|
-
const inst = await WebAssembly.instantiate(f, go.importObject);
|
|
15
|
-
go.run(inst.instance);
|
|
16
|
-
|
|
17
|
-
// TODO: Use NODE_ENV to set production mode
|
|
18
|
-
|
|
19
|
-
export default async function(configPath, config, db) {
|
|
20
|
-
if (typeof config === 'string') {
|
|
21
|
-
const conf = {value: config, isFile: true}
|
|
22
|
-
return await createGraphJin(configPath, conf, db, fs)
|
|
23
|
-
} else {
|
|
24
|
-
const conf = {value: JSON.stringify(config), isFile: false}
|
|
25
|
-
return await createGraphJin(configPath, conf, db, fs)
|
|
26
|
-
}
|
|
27
|
-
}
|
package/wasm/js/install.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import fs from "fs-extra";
|
|
2
|
-
|
|
3
|
-
import { fileURLToPath } from "url";
|
|
4
|
-
import { basename, dirname, join } from "path";
|
|
5
|
-
|
|
6
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
-
const __dirname = dirname(__filename);
|
|
8
|
-
|
|
9
|
-
const src = join(__dirname, "../config");
|
|
10
|
-
const dst = process.env.INIT_CWD;
|
|
11
|
-
|
|
12
|
-
const newDst = join(dst, basename(src));
|
|
13
|
-
|
|
14
|
-
const opt = {
|
|
15
|
-
overwrite: false,
|
|
16
|
-
errorOnExist: false,
|
|
17
|
-
dereference: true,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
await fs.emptyDir(newDst);
|
|
22
|
-
await fs.copy(src, newDst, opt);
|
|
23
|
-
} catch (err) {
|
|
24
|
-
console.error(err);
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
package/wasm/js/wasm_exec.js
DELETED
|
@@ -1,561 +0,0 @@
|
|
|
1
|
-
// Copyright 2018 The Go Authors. All rights reserved.
|
|
2
|
-
// Use of this source code is governed by a BSD-style
|
|
3
|
-
// license that can be found in the LICENSE file.
|
|
4
|
-
|
|
5
|
-
"use strict";
|
|
6
|
-
|
|
7
|
-
(() => {
|
|
8
|
-
const enosys = () => {
|
|
9
|
-
const err = new Error("not implemented");
|
|
10
|
-
err.code = "ENOSYS";
|
|
11
|
-
return err;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
if (!globalThis.fs) {
|
|
15
|
-
let outputBuf = "";
|
|
16
|
-
globalThis.fs = {
|
|
17
|
-
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
|
|
18
|
-
writeSync(fd, buf) {
|
|
19
|
-
outputBuf += decoder.decode(buf);
|
|
20
|
-
const nl = outputBuf.lastIndexOf("\n");
|
|
21
|
-
if (nl != -1) {
|
|
22
|
-
console.log(outputBuf.substring(0, nl));
|
|
23
|
-
outputBuf = outputBuf.substring(nl + 1);
|
|
24
|
-
}
|
|
25
|
-
return buf.length;
|
|
26
|
-
},
|
|
27
|
-
write(fd, buf, offset, length, position, callback) {
|
|
28
|
-
if (offset !== 0 || length !== buf.length || position !== null) {
|
|
29
|
-
callback(enosys());
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
const n = this.writeSync(fd, buf);
|
|
33
|
-
callback(null, n);
|
|
34
|
-
},
|
|
35
|
-
chmod(path, mode, callback) { callback(enosys()); },
|
|
36
|
-
chown(path, uid, gid, callback) { callback(enosys()); },
|
|
37
|
-
close(fd, callback) { callback(enosys()); },
|
|
38
|
-
fchmod(fd, mode, callback) { callback(enosys()); },
|
|
39
|
-
fchown(fd, uid, gid, callback) { callback(enosys()); },
|
|
40
|
-
fstat(fd, callback) { callback(enosys()); },
|
|
41
|
-
fsync(fd, callback) { callback(null); },
|
|
42
|
-
ftruncate(fd, length, callback) { callback(enosys()); },
|
|
43
|
-
lchown(path, uid, gid, callback) { callback(enosys()); },
|
|
44
|
-
link(path, link, callback) { callback(enosys()); },
|
|
45
|
-
lstat(path, callback) { callback(enosys()); },
|
|
46
|
-
mkdir(path, perm, callback) { callback(enosys()); },
|
|
47
|
-
open(path, flags, mode, callback) { callback(enosys()); },
|
|
48
|
-
read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
|
|
49
|
-
readdir(path, callback) { callback(enosys()); },
|
|
50
|
-
readlink(path, callback) { callback(enosys()); },
|
|
51
|
-
rename(from, to, callback) { callback(enosys()); },
|
|
52
|
-
rmdir(path, callback) { callback(enosys()); },
|
|
53
|
-
stat(path, callback) { callback(enosys()); },
|
|
54
|
-
symlink(path, link, callback) { callback(enosys()); },
|
|
55
|
-
truncate(path, length, callback) { callback(enosys()); },
|
|
56
|
-
unlink(path, callback) { callback(enosys()); },
|
|
57
|
-
utimes(path, atime, mtime, callback) { callback(enosys()); },
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (!globalThis.process) {
|
|
62
|
-
globalThis.process = {
|
|
63
|
-
getuid() { return -1; },
|
|
64
|
-
getgid() { return -1; },
|
|
65
|
-
geteuid() { return -1; },
|
|
66
|
-
getegid() { return -1; },
|
|
67
|
-
getgroups() { throw enosys(); },
|
|
68
|
-
pid: -1,
|
|
69
|
-
ppid: -1,
|
|
70
|
-
umask() { throw enosys(); },
|
|
71
|
-
cwd() { throw enosys(); },
|
|
72
|
-
chdir() { throw enosys(); },
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (!globalThis.crypto) {
|
|
77
|
-
throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (!globalThis.performance) {
|
|
81
|
-
throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (!globalThis.TextEncoder) {
|
|
85
|
-
throw new Error("globalThis.TextEncoder is not available, polyfill required");
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (!globalThis.TextDecoder) {
|
|
89
|
-
throw new Error("globalThis.TextDecoder is not available, polyfill required");
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const encoder = new TextEncoder("utf-8");
|
|
93
|
-
const decoder = new TextDecoder("utf-8");
|
|
94
|
-
|
|
95
|
-
globalThis.Go = class {
|
|
96
|
-
constructor() {
|
|
97
|
-
this.argv = ["js"];
|
|
98
|
-
this.env = {};
|
|
99
|
-
this.exit = (code) => {
|
|
100
|
-
if (code !== 0) {
|
|
101
|
-
console.warn("exit code:", code);
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
this._exitPromise = new Promise((resolve) => {
|
|
105
|
-
this._resolveExitPromise = resolve;
|
|
106
|
-
});
|
|
107
|
-
this._pendingEvent = null;
|
|
108
|
-
this._scheduledTimeouts = new Map();
|
|
109
|
-
this._nextCallbackTimeoutID = 1;
|
|
110
|
-
|
|
111
|
-
const setInt64 = (addr, v) => {
|
|
112
|
-
this.mem.setUint32(addr + 0, v, true);
|
|
113
|
-
this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const setInt32 = (addr, v) => {
|
|
117
|
-
this.mem.setUint32(addr + 0, v, true);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const getInt64 = (addr) => {
|
|
121
|
-
const low = this.mem.getUint32(addr + 0, true);
|
|
122
|
-
const high = this.mem.getInt32(addr + 4, true);
|
|
123
|
-
return low + high * 4294967296;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const loadValue = (addr) => {
|
|
127
|
-
const f = this.mem.getFloat64(addr, true);
|
|
128
|
-
if (f === 0) {
|
|
129
|
-
return undefined;
|
|
130
|
-
}
|
|
131
|
-
if (!isNaN(f)) {
|
|
132
|
-
return f;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const id = this.mem.getUint32(addr, true);
|
|
136
|
-
return this._values[id];
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const storeValue = (addr, v) => {
|
|
140
|
-
const nanHead = 0x7FF80000;
|
|
141
|
-
|
|
142
|
-
if (typeof v === "number" && v !== 0) {
|
|
143
|
-
if (isNaN(v)) {
|
|
144
|
-
this.mem.setUint32(addr + 4, nanHead, true);
|
|
145
|
-
this.mem.setUint32(addr, 0, true);
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
this.mem.setFloat64(addr, v, true);
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (v === undefined) {
|
|
153
|
-
this.mem.setFloat64(addr, 0, true);
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
let id = this._ids.get(v);
|
|
158
|
-
if (id === undefined) {
|
|
159
|
-
id = this._idPool.pop();
|
|
160
|
-
if (id === undefined) {
|
|
161
|
-
id = this._values.length;
|
|
162
|
-
}
|
|
163
|
-
this._values[id] = v;
|
|
164
|
-
this._goRefCounts[id] = 0;
|
|
165
|
-
this._ids.set(v, id);
|
|
166
|
-
}
|
|
167
|
-
this._goRefCounts[id]++;
|
|
168
|
-
let typeFlag = 0;
|
|
169
|
-
switch (typeof v) {
|
|
170
|
-
case "object":
|
|
171
|
-
if (v !== null) {
|
|
172
|
-
typeFlag = 1;
|
|
173
|
-
}
|
|
174
|
-
break;
|
|
175
|
-
case "string":
|
|
176
|
-
typeFlag = 2;
|
|
177
|
-
break;
|
|
178
|
-
case "symbol":
|
|
179
|
-
typeFlag = 3;
|
|
180
|
-
break;
|
|
181
|
-
case "function":
|
|
182
|
-
typeFlag = 4;
|
|
183
|
-
break;
|
|
184
|
-
}
|
|
185
|
-
this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
|
|
186
|
-
this.mem.setUint32(addr, id, true);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const loadSlice = (addr) => {
|
|
190
|
-
const array = getInt64(addr + 0);
|
|
191
|
-
const len = getInt64(addr + 8);
|
|
192
|
-
return new Uint8Array(this._inst.exports.mem.buffer, array, len);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const loadSliceOfValues = (addr) => {
|
|
196
|
-
const array = getInt64(addr + 0);
|
|
197
|
-
const len = getInt64(addr + 8);
|
|
198
|
-
const a = new Array(len);
|
|
199
|
-
for (let i = 0; i < len; i++) {
|
|
200
|
-
a[i] = loadValue(array + i * 8);
|
|
201
|
-
}
|
|
202
|
-
return a;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const loadString = (addr) => {
|
|
206
|
-
const saddr = getInt64(addr + 0);
|
|
207
|
-
const len = getInt64(addr + 8);
|
|
208
|
-
return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const timeOrigin = Date.now() - performance.now();
|
|
212
|
-
this.importObject = {
|
|
213
|
-
_gotest: {
|
|
214
|
-
add: (a, b) => a + b,
|
|
215
|
-
},
|
|
216
|
-
gojs: {
|
|
217
|
-
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
|
|
218
|
-
// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
|
|
219
|
-
// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
|
|
220
|
-
// This changes the SP, thus we have to update the SP used by the imported function.
|
|
221
|
-
|
|
222
|
-
// func wasmExit(code int32)
|
|
223
|
-
"runtime.wasmExit": (sp) => {
|
|
224
|
-
sp >>>= 0;
|
|
225
|
-
const code = this.mem.getInt32(sp + 8, true);
|
|
226
|
-
this.exited = true;
|
|
227
|
-
delete this._inst;
|
|
228
|
-
delete this._values;
|
|
229
|
-
delete this._goRefCounts;
|
|
230
|
-
delete this._ids;
|
|
231
|
-
delete this._idPool;
|
|
232
|
-
this.exit(code);
|
|
233
|
-
},
|
|
234
|
-
|
|
235
|
-
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
|
|
236
|
-
"runtime.wasmWrite": (sp) => {
|
|
237
|
-
sp >>>= 0;
|
|
238
|
-
const fd = getInt64(sp + 8);
|
|
239
|
-
const p = getInt64(sp + 16);
|
|
240
|
-
const n = this.mem.getInt32(sp + 24, true);
|
|
241
|
-
fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
|
|
242
|
-
},
|
|
243
|
-
|
|
244
|
-
// func resetMemoryDataView()
|
|
245
|
-
"runtime.resetMemoryDataView": (sp) => {
|
|
246
|
-
sp >>>= 0;
|
|
247
|
-
this.mem = new DataView(this._inst.exports.mem.buffer);
|
|
248
|
-
},
|
|
249
|
-
|
|
250
|
-
// func nanotime1() int64
|
|
251
|
-
"runtime.nanotime1": (sp) => {
|
|
252
|
-
sp >>>= 0;
|
|
253
|
-
setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
|
|
254
|
-
},
|
|
255
|
-
|
|
256
|
-
// func walltime() (sec int64, nsec int32)
|
|
257
|
-
"runtime.walltime": (sp) => {
|
|
258
|
-
sp >>>= 0;
|
|
259
|
-
const msec = (new Date).getTime();
|
|
260
|
-
setInt64(sp + 8, msec / 1000);
|
|
261
|
-
this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
|
|
262
|
-
},
|
|
263
|
-
|
|
264
|
-
// func scheduleTimeoutEvent(delay int64) int32
|
|
265
|
-
"runtime.scheduleTimeoutEvent": (sp) => {
|
|
266
|
-
sp >>>= 0;
|
|
267
|
-
const id = this._nextCallbackTimeoutID;
|
|
268
|
-
this._nextCallbackTimeoutID++;
|
|
269
|
-
this._scheduledTimeouts.set(id, setTimeout(
|
|
270
|
-
() => {
|
|
271
|
-
this._resume();
|
|
272
|
-
while (this._scheduledTimeouts.has(id)) {
|
|
273
|
-
// for some reason Go failed to register the timeout event, log and try again
|
|
274
|
-
// (temporary workaround for https://github.com/golang/go/issues/28975)
|
|
275
|
-
console.warn("scheduleTimeoutEvent: missed timeout event");
|
|
276
|
-
this._resume();
|
|
277
|
-
}
|
|
278
|
-
},
|
|
279
|
-
getInt64(sp + 8),
|
|
280
|
-
));
|
|
281
|
-
this.mem.setInt32(sp + 16, id, true);
|
|
282
|
-
},
|
|
283
|
-
|
|
284
|
-
// func clearTimeoutEvent(id int32)
|
|
285
|
-
"runtime.clearTimeoutEvent": (sp) => {
|
|
286
|
-
sp >>>= 0;
|
|
287
|
-
const id = this.mem.getInt32(sp + 8, true);
|
|
288
|
-
clearTimeout(this._scheduledTimeouts.get(id));
|
|
289
|
-
this._scheduledTimeouts.delete(id);
|
|
290
|
-
},
|
|
291
|
-
|
|
292
|
-
// func getRandomData(r []byte)
|
|
293
|
-
"runtime.getRandomData": (sp) => {
|
|
294
|
-
sp >>>= 0;
|
|
295
|
-
crypto.getRandomValues(loadSlice(sp + 8));
|
|
296
|
-
},
|
|
297
|
-
|
|
298
|
-
// func finalizeRef(v ref)
|
|
299
|
-
"syscall/js.finalizeRef": (sp) => {
|
|
300
|
-
sp >>>= 0;
|
|
301
|
-
const id = this.mem.getUint32(sp + 8, true);
|
|
302
|
-
this._goRefCounts[id]--;
|
|
303
|
-
if (this._goRefCounts[id] === 0) {
|
|
304
|
-
const v = this._values[id];
|
|
305
|
-
this._values[id] = null;
|
|
306
|
-
this._ids.delete(v);
|
|
307
|
-
this._idPool.push(id);
|
|
308
|
-
}
|
|
309
|
-
},
|
|
310
|
-
|
|
311
|
-
// func stringVal(value string) ref
|
|
312
|
-
"syscall/js.stringVal": (sp) => {
|
|
313
|
-
sp >>>= 0;
|
|
314
|
-
storeValue(sp + 24, loadString(sp + 8));
|
|
315
|
-
},
|
|
316
|
-
|
|
317
|
-
// func valueGet(v ref, p string) ref
|
|
318
|
-
"syscall/js.valueGet": (sp) => {
|
|
319
|
-
sp >>>= 0;
|
|
320
|
-
const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
|
|
321
|
-
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
|
322
|
-
storeValue(sp + 32, result);
|
|
323
|
-
},
|
|
324
|
-
|
|
325
|
-
// func valueSet(v ref, p string, x ref)
|
|
326
|
-
"syscall/js.valueSet": (sp) => {
|
|
327
|
-
sp >>>= 0;
|
|
328
|
-
Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
|
|
329
|
-
},
|
|
330
|
-
|
|
331
|
-
// func valueDelete(v ref, p string)
|
|
332
|
-
"syscall/js.valueDelete": (sp) => {
|
|
333
|
-
sp >>>= 0;
|
|
334
|
-
Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
|
|
335
|
-
},
|
|
336
|
-
|
|
337
|
-
// func valueIndex(v ref, i int) ref
|
|
338
|
-
"syscall/js.valueIndex": (sp) => {
|
|
339
|
-
sp >>>= 0;
|
|
340
|
-
storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
|
|
341
|
-
},
|
|
342
|
-
|
|
343
|
-
// valueSetIndex(v ref, i int, x ref)
|
|
344
|
-
"syscall/js.valueSetIndex": (sp) => {
|
|
345
|
-
sp >>>= 0;
|
|
346
|
-
Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
|
|
347
|
-
},
|
|
348
|
-
|
|
349
|
-
// func valueCall(v ref, m string, args []ref) (ref, bool)
|
|
350
|
-
"syscall/js.valueCall": (sp) => {
|
|
351
|
-
sp >>>= 0;
|
|
352
|
-
try {
|
|
353
|
-
const v = loadValue(sp + 8);
|
|
354
|
-
const m = Reflect.get(v, loadString(sp + 16));
|
|
355
|
-
const args = loadSliceOfValues(sp + 32);
|
|
356
|
-
const result = Reflect.apply(m, v, args);
|
|
357
|
-
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
|
358
|
-
storeValue(sp + 56, result);
|
|
359
|
-
this.mem.setUint8(sp + 64, 1);
|
|
360
|
-
} catch (err) {
|
|
361
|
-
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
|
362
|
-
storeValue(sp + 56, err);
|
|
363
|
-
this.mem.setUint8(sp + 64, 0);
|
|
364
|
-
}
|
|
365
|
-
},
|
|
366
|
-
|
|
367
|
-
// func valueInvoke(v ref, args []ref) (ref, bool)
|
|
368
|
-
"syscall/js.valueInvoke": (sp) => {
|
|
369
|
-
sp >>>= 0;
|
|
370
|
-
try {
|
|
371
|
-
const v = loadValue(sp + 8);
|
|
372
|
-
const args = loadSliceOfValues(sp + 16);
|
|
373
|
-
const result = Reflect.apply(v, undefined, args);
|
|
374
|
-
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
|
375
|
-
storeValue(sp + 40, result);
|
|
376
|
-
this.mem.setUint8(sp + 48, 1);
|
|
377
|
-
} catch (err) {
|
|
378
|
-
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
|
379
|
-
storeValue(sp + 40, err);
|
|
380
|
-
this.mem.setUint8(sp + 48, 0);
|
|
381
|
-
}
|
|
382
|
-
},
|
|
383
|
-
|
|
384
|
-
// func valueNew(v ref, args []ref) (ref, bool)
|
|
385
|
-
"syscall/js.valueNew": (sp) => {
|
|
386
|
-
sp >>>= 0;
|
|
387
|
-
try {
|
|
388
|
-
const v = loadValue(sp + 8);
|
|
389
|
-
const args = loadSliceOfValues(sp + 16);
|
|
390
|
-
const result = Reflect.construct(v, args);
|
|
391
|
-
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
|
392
|
-
storeValue(sp + 40, result);
|
|
393
|
-
this.mem.setUint8(sp + 48, 1);
|
|
394
|
-
} catch (err) {
|
|
395
|
-
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
|
396
|
-
storeValue(sp + 40, err);
|
|
397
|
-
this.mem.setUint8(sp + 48, 0);
|
|
398
|
-
}
|
|
399
|
-
},
|
|
400
|
-
|
|
401
|
-
// func valueLength(v ref) int
|
|
402
|
-
"syscall/js.valueLength": (sp) => {
|
|
403
|
-
sp >>>= 0;
|
|
404
|
-
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
|
|
405
|
-
},
|
|
406
|
-
|
|
407
|
-
// valuePrepareString(v ref) (ref, int)
|
|
408
|
-
"syscall/js.valuePrepareString": (sp) => {
|
|
409
|
-
sp >>>= 0;
|
|
410
|
-
const str = encoder.encode(String(loadValue(sp + 8)));
|
|
411
|
-
storeValue(sp + 16, str);
|
|
412
|
-
setInt64(sp + 24, str.length);
|
|
413
|
-
},
|
|
414
|
-
|
|
415
|
-
// valueLoadString(v ref, b []byte)
|
|
416
|
-
"syscall/js.valueLoadString": (sp) => {
|
|
417
|
-
sp >>>= 0;
|
|
418
|
-
const str = loadValue(sp + 8);
|
|
419
|
-
loadSlice(sp + 16).set(str);
|
|
420
|
-
},
|
|
421
|
-
|
|
422
|
-
// func valueInstanceOf(v ref, t ref) bool
|
|
423
|
-
"syscall/js.valueInstanceOf": (sp) => {
|
|
424
|
-
sp >>>= 0;
|
|
425
|
-
this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
|
|
426
|
-
},
|
|
427
|
-
|
|
428
|
-
// func copyBytesToGo(dst []byte, src ref) (int, bool)
|
|
429
|
-
"syscall/js.copyBytesToGo": (sp) => {
|
|
430
|
-
sp >>>= 0;
|
|
431
|
-
const dst = loadSlice(sp + 8);
|
|
432
|
-
const src = loadValue(sp + 32);
|
|
433
|
-
if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
|
|
434
|
-
this.mem.setUint8(sp + 48, 0);
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
const toCopy = src.subarray(0, dst.length);
|
|
438
|
-
dst.set(toCopy);
|
|
439
|
-
setInt64(sp + 40, toCopy.length);
|
|
440
|
-
this.mem.setUint8(sp + 48, 1);
|
|
441
|
-
},
|
|
442
|
-
|
|
443
|
-
// func copyBytesToJS(dst ref, src []byte) (int, bool)
|
|
444
|
-
"syscall/js.copyBytesToJS": (sp) => {
|
|
445
|
-
sp >>>= 0;
|
|
446
|
-
const dst = loadValue(sp + 8);
|
|
447
|
-
const src = loadSlice(sp + 16);
|
|
448
|
-
if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
|
|
449
|
-
this.mem.setUint8(sp + 48, 0);
|
|
450
|
-
return;
|
|
451
|
-
}
|
|
452
|
-
const toCopy = src.subarray(0, dst.length);
|
|
453
|
-
dst.set(toCopy);
|
|
454
|
-
setInt64(sp + 40, toCopy.length);
|
|
455
|
-
this.mem.setUint8(sp + 48, 1);
|
|
456
|
-
},
|
|
457
|
-
|
|
458
|
-
"debug": (value) => {
|
|
459
|
-
console.log(value);
|
|
460
|
-
},
|
|
461
|
-
}
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
async run(instance) {
|
|
466
|
-
if (!(instance instanceof WebAssembly.Instance)) {
|
|
467
|
-
throw new Error("Go.run: WebAssembly.Instance expected");
|
|
468
|
-
}
|
|
469
|
-
this._inst = instance;
|
|
470
|
-
this.mem = new DataView(this._inst.exports.mem.buffer);
|
|
471
|
-
this._values = [ // JS values that Go currently has references to, indexed by reference id
|
|
472
|
-
NaN,
|
|
473
|
-
0,
|
|
474
|
-
null,
|
|
475
|
-
true,
|
|
476
|
-
false,
|
|
477
|
-
globalThis,
|
|
478
|
-
this,
|
|
479
|
-
];
|
|
480
|
-
this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
|
|
481
|
-
this._ids = new Map([ // mapping from JS values to reference ids
|
|
482
|
-
[0, 1],
|
|
483
|
-
[null, 2],
|
|
484
|
-
[true, 3],
|
|
485
|
-
[false, 4],
|
|
486
|
-
[globalThis, 5],
|
|
487
|
-
[this, 6],
|
|
488
|
-
]);
|
|
489
|
-
this._idPool = []; // unused ids that have been garbage collected
|
|
490
|
-
this.exited = false; // whether the Go program has exited
|
|
491
|
-
|
|
492
|
-
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
|
|
493
|
-
let offset = 4096;
|
|
494
|
-
|
|
495
|
-
const strPtr = (str) => {
|
|
496
|
-
const ptr = offset;
|
|
497
|
-
const bytes = encoder.encode(str + "\0");
|
|
498
|
-
new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
|
|
499
|
-
offset += bytes.length;
|
|
500
|
-
if (offset % 8 !== 0) {
|
|
501
|
-
offset += 8 - (offset % 8);
|
|
502
|
-
}
|
|
503
|
-
return ptr;
|
|
504
|
-
};
|
|
505
|
-
|
|
506
|
-
const argc = this.argv.length;
|
|
507
|
-
|
|
508
|
-
const argvPtrs = [];
|
|
509
|
-
this.argv.forEach((arg) => {
|
|
510
|
-
argvPtrs.push(strPtr(arg));
|
|
511
|
-
});
|
|
512
|
-
argvPtrs.push(0);
|
|
513
|
-
|
|
514
|
-
const keys = Object.keys(this.env).sort();
|
|
515
|
-
keys.forEach((key) => {
|
|
516
|
-
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
|
|
517
|
-
});
|
|
518
|
-
argvPtrs.push(0);
|
|
519
|
-
|
|
520
|
-
const argv = offset;
|
|
521
|
-
argvPtrs.forEach((ptr) => {
|
|
522
|
-
this.mem.setUint32(offset, ptr, true);
|
|
523
|
-
this.mem.setUint32(offset + 4, 0, true);
|
|
524
|
-
offset += 8;
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
// The linker guarantees global data starts from at least wasmMinDataAddr.
|
|
528
|
-
// Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
|
|
529
|
-
const wasmMinDataAddr = 4096 + 8192;
|
|
530
|
-
if (offset >= wasmMinDataAddr) {
|
|
531
|
-
throw new Error("total length of command line and environment variables exceeds limit");
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
this._inst.exports.run(argc, argv);
|
|
535
|
-
if (this.exited) {
|
|
536
|
-
this._resolveExitPromise();
|
|
537
|
-
}
|
|
538
|
-
await this._exitPromise;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
_resume() {
|
|
542
|
-
if (this.exited) {
|
|
543
|
-
throw new Error("Go program has already exited");
|
|
544
|
-
}
|
|
545
|
-
this._inst.exports.resume();
|
|
546
|
-
if (this.exited) {
|
|
547
|
-
this._resolveExitPromise();
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
_makeFuncWrapper(id) {
|
|
552
|
-
const go = this;
|
|
553
|
-
return function () {
|
|
554
|
-
const event = { id: id, this: this, args: arguments };
|
|
555
|
-
go._pendingEvent = event;
|
|
556
|
-
go._resume();
|
|
557
|
-
return event.result;
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
})();
|