graphvault-studio 0.1.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/CHANGELOG.md +17 -0
- package/CONTRIBUTING.md +27 -0
- package/LICENSE +21 -0
- package/README.md +119 -0
- package/assets/graphvault-logo.png +0 -0
- package/assets/studio-screenshot.png +0 -0
- package/dist/admin-cli.d.ts +12 -0
- package/dist/admin-cli.js +132 -0
- package/dist/admin-cli.js.map +1 -0
- package/dist/admin-client.d.ts +49 -0
- package/dist/admin-client.js +352 -0
- package/dist/admin-client.js.map +1 -0
- package/dist/admin-inspection.d.ts +4 -0
- package/dist/admin-inspection.js +70 -0
- package/dist/admin-inspection.js.map +1 -0
- package/dist/admin-mutation.d.ts +4 -0
- package/dist/admin-mutation.js +83 -0
- package/dist/admin-mutation.js.map +1 -0
- package/dist/admin-parent-index.d.ts +4 -0
- package/dist/admin-parent-index.js +72 -0
- package/dist/admin-parent-index.js.map +1 -0
- package/dist/admin-server.d.ts +14 -0
- package/dist/admin-server.js +132 -0
- package/dist/admin-server.js.map +1 -0
- package/dist/admin-types.d.ts +97 -0
- package/dist/admin-types.js +2 -0
- package/dist/admin-types.js.map +1 -0
- package/dist/admin-ui.d.ts +1 -0
- package/dist/admin-ui.js +886 -0
- package/dist/admin-ui.js.map +1 -0
- package/dist/admin.d.ts +4 -0
- package/dist/admin.js +3 -0
- package/dist/admin.js.map +1 -0
- package/dist/gvql-examples.d.ts +6 -0
- package/dist/gvql-examples.js +45 -0
- package/dist/gvql-examples.js.map +1 -0
- package/docs/GVQL.md +217 -0
- package/docs/PUBLISHING.md +46 -0
- package/docs/RELEASE_NOTES_0.1.0.md +41 -0
- package/docs/REMOTE_STORAGE.md +56 -0
- package/examples/create-demo-store.mjs +85 -0
- package/package.json +67 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-ui.js","sourceRoot":"","sources":["../src/admin-ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1D,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAqNC,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA8pBvD,CAAC"}
|
package/dist/admin.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { StorageAdminClient } from "./admin-client.js";
|
|
2
|
+
export type { AdminGraph, AdminGraphEdge, AdminGraphNode, AdminHierarchyPath, AdminHierarchyPathItem, AdminMutation, AdminMutationPreview, GvqlResult, AdminObjectChild, AdminObjectListItem, AdminObjectPage, AdminObjectParent, AdminRootReference, AdminSearchResult, AdminSummary, StorageAdminClientOptions, } from "./admin-client.js";
|
|
3
|
+
export { startAdminServer } from "./admin-server.js";
|
|
4
|
+
export type { AdminServerOptions, RunningAdminServer } from "./admin-server.js";
|
package/dist/admin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin.js","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAmBvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export const STUDIO_GVQL_EXAMPLES = [
|
|
2
|
+
{ name: "Inspect nodes", query: "MATCH (node) RETURN node.$id AS objectId, node.$type AS type, node.$kind AS kind ORDER BY node.$id ASC LIMIT 25 OFFSET 0" },
|
|
3
|
+
{ name: "Filter types", query: 'MATCH (node) WHERE node.$type IN ["Document", "Workspace"] RETURN node.$id AS objectId, node.$type AS type ORDER BY node.$type ASC, node.$id ASC' },
|
|
4
|
+
{ name: "Distinct values", query: "MATCH (item) RETURN DISTINCT item.status AS status ORDER BY status ASC" },
|
|
5
|
+
{ name: "Type count", query: "MATCH (node) WHERE node.$type IS NOT NULL RETURN count(DISTINCT node.$type) AS types" },
|
|
6
|
+
{ name: "Nullable fields", query: "MATCH (item) WHERE item.archivedAt IS NULL AND item.status IS NOT NULL RETURN item.id AS id, item.title AS title ORDER BY item.id ASC" },
|
|
7
|
+
{ name: "Indexed IN", query: 'MATCH (item) WHERE item.status IN ["draft", "published"] AND item.id IN ["doc-1", "doc-2"] RETURN item.id AS id, item.status AS status ORDER BY item.id ASC' },
|
|
8
|
+
{ name: "Indexed OR", query: 'MATCH (item) WHERE item.id = "missing" OR item.status = "published" RETURN item.id AS id, item.status AS status' },
|
|
9
|
+
{ name: "Parentheses", query: 'MATCH (item) WHERE (item.status = "draft" OR item.status = "published") AND item.views > 20 RETURN item.id AS id, item.status AS status, item.views AS views' },
|
|
10
|
+
{ name: "NOT filter", query: 'MATCH (item) WHERE NOT (item.status = "published" OR item.views < 10) RETURN item.id AS id, item.status AS status, item.views AS views' },
|
|
11
|
+
{ name: "Computed score", query: "MATCH (item) RETURN item.id AS id, (item.views + $bonus) * 2 AS score ORDER BY score DESC LIMIT 25", parameters: { bonus: 5 } },
|
|
12
|
+
{ name: "Aggregate", query: "MATCH (item) RETURN item.status AS status, count(*) AS count GROUP BY item.status HAVING count > 1 ORDER BY count DESC, status ASC" },
|
|
13
|
+
{ name: "Aggregate NOT", query: 'MATCH (item) RETURN item.status AS status, count(*) AS count, avg(item.views) AS avgViews GROUP BY item.status HAVING NOT (status = "published" OR avgViews < 10) ORDER BY status ASC' },
|
|
14
|
+
{
|
|
15
|
+
name: "WITH pipeline",
|
|
16
|
+
query: "MATCH (item) WHERE item.status IS NOT NULL WITH item.status AS status, count(*) AS count, avg(item.views) AS avgViews GROUP BY item.status HAVING count > 0 RETURN status, count, avgViews ORDER BY count DESC",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "WITH filter",
|
|
20
|
+
query: 'MATCH (item) WHERE item.status IS NOT NULL WITH item.id AS id, CASE WHEN item.views >= 100 THEN "hot" WHEN item.archivedAt IS NOT NULL THEN "archived" ELSE "active" END AS bucket WHERE bucket = "hot" RETURN id, bucket ORDER BY id ASC',
|
|
21
|
+
},
|
|
22
|
+
{ name: "Traverse owner", query: 'MATCH (item)-[:owner]->(owner) WHERE owner.name = "Platform Team" RETURN item.title AS title' },
|
|
23
|
+
{ name: "Join patterns", query: 'MATCH (item)-[:owner]->(owner), (item)-[:category]->(category) WHERE owner.name = "Platform Team" AND category.slug = "guides" RETURN item.id AS id, item.title AS title, category.label AS category ORDER BY item.title ASC LIMIT 25' },
|
|
24
|
+
{ name: "Optional match", query: "MATCH (item) OPTIONAL MATCH (item)-[:related]->(items)-[:*]->(related) RETURN item.id AS id, related.id AS relatedId ORDER BY item.id ASC LIMIT 25" },
|
|
25
|
+
{
|
|
26
|
+
name: "Scalar functions",
|
|
27
|
+
query: 'MATCH (item) WHERE lower(item.title) CONTAINS lower($needle) RETURN item.id AS id, upper(trim(item.title)) AS title, length(item.title) AS titleLength, coalesce(item.archivedAt, "none") AS archived ORDER BY item.id ASC',
|
|
28
|
+
parameters: { needle: "storage" },
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: "CASE buckets",
|
|
32
|
+
query: 'MATCH (item) WHERE item.status IS NOT NULL RETURN item.id AS id, CASE WHEN item.views >= 100 THEN "hot" WHEN item.archivedAt IS NOT NULL THEN "archived" ELSE "active" END AS bucket ORDER BY item.id ASC LIMIT 25',
|
|
33
|
+
},
|
|
34
|
+
{ name: "Preview SET", query: 'MATCH (item) WHERE item.status = "draft" SET item.status = "archived" RETURN count(*) AS changed' },
|
|
35
|
+
{
|
|
36
|
+
name: "CASE update",
|
|
37
|
+
query: 'MATCH (item) WHERE item.status IS NOT NULL SET item.status = CASE WHEN item.views >= 100 THEN "featured" WHEN item.archivedAt IS NOT NULL THEN "archived" ELSE item.status END RETURN item.id AS id, item.status AS status',
|
|
38
|
+
},
|
|
39
|
+
{ name: "Arithmetic SET", query: "MATCH (item) WHERE item.status = \"published\" SET item.views = (item.views + $increment) * 2 RETURN item.id AS id, item.views AS views", parameters: { increment: 5 } },
|
|
40
|
+
{ name: "REMOVE field", query: "MATCH (item) WHERE item.archivedAt IS NOT NULL REMOVE item.archivedAt RETURN count(*) AS changed" },
|
|
41
|
+
{ name: "DELETE object", query: 'MATCH (item) WHERE item.status = "archived" DELETE item RETURN item.id AS id' },
|
|
42
|
+
{ name: "CREATE into collection", query: 'MATCH (workspace:Workspace) WHERE workspace.name = "Developer docs" CREATE (item:Document { id: "doc-4", title: "Release checklist", status: "draft", views: 0 }) INTO workspace.documents RETURN item.id AS id, item.title AS title' },
|
|
43
|
+
{ name: "MERGE into collection", query: 'MATCH (workspace:Workspace) WHERE workspace.name = "Developer docs" MERGE (item:Document { id: "doc-4", title: "Release checklist", status: "draft", views: 0 }) INTO workspace.documents ON item.id RETURN item.id AS id, item.title AS title' },
|
|
44
|
+
];
|
|
45
|
+
//# sourceMappingURL=gvql-examples.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gvql-examples.js","sourceRoot":"","sources":["../src/gvql-examples.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,MAAM,oBAAoB,GAAwB;IACvD,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,0HAA0H,EAAE;IAC5J,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,kJAAkJ,EAAE;IACnL,EAAE,IAAI,EAAE,iBAAiB,EAAE,KAAK,EAAE,wEAAwE,EAAE;IAC5G,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,sFAAsF,EAAE;IACrH,EAAE,IAAI,EAAE,iBAAiB,EAAE,KAAK,EAAE,uIAAuI,EAAE;IAC3K,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,6JAA6J,EAAE;IAC5L,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,iHAAiH,EAAE;IAChJ,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,8JAA8J,EAAE;IAC9L,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,wIAAwI,EAAE;IACvK,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,oGAAoG,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IACjK,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,oIAAoI,EAAE;IAClK,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,uLAAuL,EAAE;IACzN;QACE,IAAI,EAAE,eAAe;QACrB,KAAK,EAAE,gNAAgN;KACxN;IACD;QACE,IAAI,EAAE,aAAa;QACnB,KAAK,EAAE,2OAA2O;KACnP;IACD,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,8FAA8F,EAAE;IACjI,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,uOAAuO,EAAE;IACzQ,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,oJAAoJ,EAAE;IACvL;QACE,IAAI,EAAE,kBAAkB;QACxB,KAAK,EAAE,4NAA4N;QACnO,UAAU,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE;KAClC;IACD;QACE,IAAI,EAAE,cAAc;QACpB,KAAK,EAAE,oNAAoN;KAC5N;IACD,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,kGAAkG,EAAE;IAClI;QACE,IAAI,EAAE,aAAa;QACnB,KAAK,EAAE,4NAA4N;KACpO;IACD,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,yIAAyI,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE;IAC1M,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,kGAAkG,EAAE;IACnI,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,8EAA8E,EAAE;IAChH,EAAE,IAAI,EAAE,wBAAwB,EAAE,KAAK,EAAE,sOAAsO,EAAE;IACjR,EAAE,IAAI,EAAE,uBAAuB,EAAE,KAAK,EAAE,gPAAgP,EAAE;CAC3R,CAAC"}
|
package/docs/GVQL.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# GraphVault Studio GVQL Examples
|
|
2
|
+
|
|
3
|
+
## GVQL
|
|
4
|
+
|
|
5
|
+
Studio includes a GVQL console for GraphVault stores. It supports read queries and safe batch-update previews:
|
|
6
|
+
|
|
7
|
+
```sql
|
|
8
|
+
MATCH (node)
|
|
9
|
+
RETURN node.$id AS objectId, node.$type AS type, node.$kind AS kind
|
|
10
|
+
ORDER BY node.$id ASC
|
|
11
|
+
LIMIT 25
|
|
12
|
+
OFFSET 0
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```sql
|
|
16
|
+
MATCH (node)
|
|
17
|
+
WHERE node.$type IN ["Document", "Workspace"]
|
|
18
|
+
RETURN node.$id AS objectId, node.$type AS type
|
|
19
|
+
ORDER BY node.$type ASC, node.$id ASC
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```sql
|
|
23
|
+
MATCH (doc:Document)-[:owner]->(owner:Owner)
|
|
24
|
+
WHERE owner.name = "Platform Team"
|
|
25
|
+
RETURN doc.id AS id, doc.title AS title
|
|
26
|
+
ORDER BY doc.title ASC, doc.id ASC
|
|
27
|
+
LIMIT 25
|
|
28
|
+
OFFSET 0
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```sql
|
|
32
|
+
MATCH (doc:Document)-[:owner]->(owner:Owner), (doc)-[:category]->(category:Category)
|
|
33
|
+
WHERE owner.name = "Platform Team" AND category.slug = "guides"
|
|
34
|
+
RETURN doc.id AS id, doc.title AS title, category.label AS category
|
|
35
|
+
ORDER BY doc.title ASC
|
|
36
|
+
LIMIT 25
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```sql
|
|
40
|
+
MATCH (doc:Document)
|
|
41
|
+
OPTIONAL MATCH (doc)-[:related]->(items)-[:*]->(related:Document)
|
|
42
|
+
RETURN doc.id AS id, related.id AS relatedId
|
|
43
|
+
ORDER BY doc.id ASC
|
|
44
|
+
LIMIT 25
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```sql
|
|
48
|
+
MATCH (doc:Document)
|
|
49
|
+
WHERE lower(doc.title) CONTAINS lower($needle)
|
|
50
|
+
RETURN doc.id AS id, upper(trim(doc.title)) AS title, length(doc.title) AS titleLength, coalesce(doc.archivedAt, "none") AS archived
|
|
51
|
+
ORDER BY doc.id ASC
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```sql
|
|
55
|
+
MATCH (doc:Document)
|
|
56
|
+
RETURN DISTINCT doc.status AS status
|
|
57
|
+
ORDER BY status ASC
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```sql
|
|
61
|
+
MATCH (node)
|
|
62
|
+
WHERE node.$type IS NOT NULL
|
|
63
|
+
RETURN count(DISTINCT node.$type) AS types
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```sql
|
|
67
|
+
MATCH (doc:Document)
|
|
68
|
+
WHERE doc.archivedAt IS NULL AND doc.status IS NOT NULL
|
|
69
|
+
RETURN doc.id AS id, doc.title AS title
|
|
70
|
+
ORDER BY doc.id ASC
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```sql
|
|
74
|
+
MATCH (doc:Document)
|
|
75
|
+
WHERE doc.status IN ["draft", "published"] AND doc.id IN ["doc-1", "doc-2"]
|
|
76
|
+
RETURN doc.id AS id, doc.status AS status
|
|
77
|
+
ORDER BY doc.id ASC
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```sql
|
|
81
|
+
MATCH (doc:Document)
|
|
82
|
+
WHERE doc.id = "missing" OR doc.status = "published"
|
|
83
|
+
RETURN doc.id AS id, doc.status AS status
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```sql
|
|
87
|
+
MATCH (doc:Document)
|
|
88
|
+
WHERE (doc.status = "draft" OR doc.status = "published") AND doc.views > 20
|
|
89
|
+
RETURN doc.id AS id, doc.status AS status, doc.views AS views
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```sql
|
|
93
|
+
MATCH (doc:Document)
|
|
94
|
+
WHERE NOT (doc.status = "published" OR doc.views < 10)
|
|
95
|
+
RETURN doc.id AS id, doc.status AS status, doc.views AS views
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```sql
|
|
99
|
+
MATCH (doc:Document)
|
|
100
|
+
RETURN doc.id AS id, (doc.views + $bonus) * 2 AS score
|
|
101
|
+
ORDER BY score DESC
|
|
102
|
+
LIMIT 25
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```sql
|
|
106
|
+
MATCH (doc:Document)
|
|
107
|
+
WHERE doc.status IS NOT NULL
|
|
108
|
+
RETURN doc.id AS id,
|
|
109
|
+
CASE
|
|
110
|
+
WHEN doc.views >= 100 THEN "hot"
|
|
111
|
+
WHEN doc.archivedAt IS NOT NULL THEN "archived"
|
|
112
|
+
ELSE "active"
|
|
113
|
+
END AS bucket
|
|
114
|
+
ORDER BY doc.id ASC
|
|
115
|
+
LIMIT 25
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```sql
|
|
119
|
+
MATCH (doc:Document)
|
|
120
|
+
WHERE doc.status IS NOT NULL
|
|
121
|
+
WITH doc.status AS status, count(*) AS count, avg(doc.views) AS avgViews
|
|
122
|
+
GROUP BY doc.status
|
|
123
|
+
HAVING count > 0
|
|
124
|
+
RETURN status, count, avgViews
|
|
125
|
+
ORDER BY count DESC
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
```sql
|
|
129
|
+
MATCH (doc:Document)
|
|
130
|
+
WHERE doc.status IS NOT NULL
|
|
131
|
+
WITH doc.id AS id,
|
|
132
|
+
CASE
|
|
133
|
+
WHEN doc.views >= 100 THEN "hot"
|
|
134
|
+
WHEN doc.archivedAt IS NOT NULL THEN "archived"
|
|
135
|
+
ELSE "active"
|
|
136
|
+
END AS bucket
|
|
137
|
+
WHERE bucket = "hot"
|
|
138
|
+
RETURN id, bucket
|
|
139
|
+
ORDER BY id ASC
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```sql
|
|
143
|
+
MATCH (doc:Document)
|
|
144
|
+
WHERE doc.status = "draft"
|
|
145
|
+
SET doc.status = "archived"
|
|
146
|
+
RETURN count(*) AS changed
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
```sql
|
|
150
|
+
MATCH (doc:Document)
|
|
151
|
+
WHERE doc.status IS NOT NULL
|
|
152
|
+
SET doc.status =
|
|
153
|
+
CASE
|
|
154
|
+
WHEN doc.views >= 100 THEN "featured"
|
|
155
|
+
WHEN doc.archivedAt IS NOT NULL THEN "archived"
|
|
156
|
+
ELSE doc.status
|
|
157
|
+
END
|
|
158
|
+
RETURN doc.id AS id, doc.status AS status
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
```sql
|
|
162
|
+
MATCH (doc:Document)
|
|
163
|
+
WHERE doc.status = "published"
|
|
164
|
+
SET doc.views = (doc.views + $increment) * 2
|
|
165
|
+
RETURN doc.id AS id, doc.views AS views
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
```sql
|
|
169
|
+
MATCH (doc:Document)
|
|
170
|
+
WHERE doc.archivedAt IS NOT NULL
|
|
171
|
+
REMOVE doc.archivedAt
|
|
172
|
+
RETURN count(*) AS changed
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
```sql
|
|
176
|
+
MATCH (doc:Document)
|
|
177
|
+
WHERE doc.status = "archived"
|
|
178
|
+
DELETE doc
|
|
179
|
+
RETURN doc.id AS id
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
```sql
|
|
183
|
+
MATCH (workspace:Workspace)
|
|
184
|
+
WHERE workspace.name = "Developer docs"
|
|
185
|
+
CREATE (doc:Document { id: "doc-4", title: "Release checklist", status: "draft", views: 0 }) INTO workspace.documents
|
|
186
|
+
RETURN doc.id AS id, doc.title AS title
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
```sql
|
|
190
|
+
MATCH (workspace:Workspace)
|
|
191
|
+
WHERE workspace.name = "Developer docs"
|
|
192
|
+
MERGE (doc:Document { id: "doc-4", title: "Release checklist", status: "draft", views: 0 }) INTO workspace.documents ON doc.id
|
|
193
|
+
RETURN doc.id AS id, doc.title AS title
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Aggregate queries can be inspected directly in the same console:
|
|
197
|
+
|
|
198
|
+
```sql
|
|
199
|
+
MATCH (item)
|
|
200
|
+
RETURN item.status AS status, count(*) AS count
|
|
201
|
+
GROUP BY item.status
|
|
202
|
+
HAVING count > 1
|
|
203
|
+
ORDER BY count DESC, status ASC
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
```sql
|
|
207
|
+
MATCH (item)
|
|
208
|
+
RETURN item.status AS status, count(*) AS count, avg(item.views) AS avgViews
|
|
209
|
+
GROUP BY item.status
|
|
210
|
+
HAVING NOT (status = "published" OR avgViews < 10)
|
|
211
|
+
ORDER BY status ASC
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`Run / Preview` executes read queries and dry-runs updates. `Commit GVQL` applies update statements only when Studio was started with mutation support and the confirmation token matches.
|
|
215
|
+
|
|
216
|
+
Each GVQL run includes an execution-plan row that shows whether Studio used a type index, primitive-property index, indexed `OR` union, or full scan, plus candidate and returned-row counts. That makes slow queries much easier to tune before they become production habits.
|
|
217
|
+
Use `LIMIT` and `OFFSET` in the console for predictable paging through large result sets. Query parameters such as `$needle` or `$increment` are detected automatically and shown as typed input fields, so you do not need to hand-edit a JSON payload for common runs. Comma-separated `MATCH` patterns let you express join-like graph queries where shared aliases must resolve to the same object. `OPTIONAL MATCH` keeps the primary rows visible when a relationship is missing. `WITH` pipelines let you name intermediate values, aggregate them, and filter row aliases before the final `RETURN`. Computed `RETURN` expressions, `CASE` buckets, and scalar functions such as `lower`, `upper`, `trim`, `length`, and `coalesce` are useful for quick scores, projections, normalization, and sanity checks without changing stored data. `RETURN DISTINCT` and `count(DISTINCT path)` are useful when you want compact lists or cardinality checks for values such as statuses, tenants, regions, types, or object categories. Parentheses and `NOT` in `WHERE` and `HAVING` make mixed filters predictable. `CREATE ... INTO`, idempotent `MERGE ... INTO ... ON`, arithmetic or conditional `SET` expressions, `IS NULL`, `IS NOT NULL`, `REMOVE`, and parent-aware `DELETE` make graph manipulation previewable before commit.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Publishing GraphVault Studio
|
|
2
|
+
|
|
3
|
+
This checklist keeps the first public admin-client release repeatable.
|
|
4
|
+
|
|
5
|
+
## Preconditions
|
|
6
|
+
|
|
7
|
+
- `@sprengmeister/graphvault` has already been released.
|
|
8
|
+
- `package.json` has the intended `name`, `version`, `repository`, `homepage`, `bugs`, `license`, `engines`, `bin`, `exports`, and `files`.
|
|
9
|
+
- `CHANGELOG.md` and `docs/RELEASE_NOTES_0.1.0.md` describe the release.
|
|
10
|
+
- `NPM_TOKEN` is configured as a GitHub Actions repository secret for npm publishing.
|
|
11
|
+
|
|
12
|
+
## Local Release Check
|
|
13
|
+
|
|
14
|
+
Run these from the repository root:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm ci
|
|
18
|
+
npm test
|
|
19
|
+
npm run demo:store
|
|
20
|
+
npm run pack:dry-run
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Inspect the dry-run file list and confirm it contains `README.md`, `LICENSE`, `CHANGELOG.md`, `CONTRIBUTING.md`, `docs`, `dist`, `examples`, logo/screenshot assets, and the `graphvault-studio` CLI entry point.
|
|
24
|
+
|
|
25
|
+
## Tagging
|
|
26
|
+
|
|
27
|
+
Create the release tag only after the local release check passes:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
git tag -a v0.1.0 -m "GraphVault Studio 0.1.0"
|
|
31
|
+
git push origin v0.1.0
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Publishing
|
|
35
|
+
|
|
36
|
+
Use the GitHub Actions `Release` workflow with the matching tag input, for example `v0.1.0`.
|
|
37
|
+
|
|
38
|
+
The workflow checks out the tag, installs with `npm ci`, runs tests, creates the demo store, validates the npm tarball with `npm run pack:dry-run`, and publishes with npm provenance.
|
|
39
|
+
|
|
40
|
+
## Repository Visibility
|
|
41
|
+
|
|
42
|
+
Recommended GitHub topics:
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
typescript, admin-ui, database-admin, graph-database, object-graph, storage-browser, gvql, developer-tools
|
|
46
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# GraphVault Studio 0.1.0 Release Notes
|
|
2
|
+
|
|
3
|
+
GraphVault Studio 0.1.0 is the first public graphical admin client for GraphVault object graph stores.
|
|
4
|
+
|
|
5
|
+
## What Is Included
|
|
6
|
+
|
|
7
|
+
- Root-first hierarchy browser for object graphs.
|
|
8
|
+
- Search across paths, values, ids, types, references, and encoded object data.
|
|
9
|
+
- Parent-path lookup from an object back toward the root, including multiple direct parents.
|
|
10
|
+
- Paged object browser for large stores.
|
|
11
|
+
- GVQL console for graph queries and dry-run batch mutation previews.
|
|
12
|
+
- Controlled primitive-field editing with confirmation-token safety.
|
|
13
|
+
- Verification, maintenance, backup, transaction, journal, graph, type dictionary, and object detail views.
|
|
14
|
+
- CLI and programmatic server startup.
|
|
15
|
+
- Local filesystem, custom, remote, S3-compatible, HTTP, and SQL-backed storage through GraphVault storage targets.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install graphvault-studio
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The package is ready for npm registry publishing with the CLI binary:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx graphvault-studio --dir ./data --port 4177
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Demo
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm ci
|
|
33
|
+
npm run demo:store
|
|
34
|
+
npx graphvault-studio --dir ./graphvault-studio-demo-store --port 4177 --allow-mutations --confirm-token confirm
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Open `http://127.0.0.1:4177`.
|
|
38
|
+
|
|
39
|
+
## Recommended GitHub Topics
|
|
40
|
+
|
|
41
|
+
`typescript`, `admin-ui`, `developer-tools`, `graph-database`, `object-graph`, `storage-browser`, `database-admin`, `gvql`, `local-first`, `nestjs`
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Remote And Custom Storage
|
|
2
|
+
|
|
3
|
+
### Remote Or Custom Storage
|
|
4
|
+
|
|
5
|
+
For remote storage, start Studio programmatically and pass the same `storageTarget` adapter your app uses. `storageDirectory` is still required; for remote targets it acts as the key prefix or logical root path inside the target.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { S3StorageTarget } from "@sprengmeister/graphvault";
|
|
9
|
+
import { startAdminServer } from "graphvault-studio";
|
|
10
|
+
|
|
11
|
+
await startAdminServer({
|
|
12
|
+
storageDirectory: "prod/app-store",
|
|
13
|
+
storageTarget: new S3StorageTarget({
|
|
14
|
+
bucket: "graphvault-prod",
|
|
15
|
+
prefix: "stores",
|
|
16
|
+
client: s3ClientAdapter,
|
|
17
|
+
}),
|
|
18
|
+
port: 4177,
|
|
19
|
+
authToken: process.env.GRAPHVAULT_ADMIN_TOKEN,
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
HTTP-backed storage works the same way:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { HttpStorageTarget } from "@sprengmeister/graphvault";
|
|
27
|
+
import { startAdminServer } from "graphvault-studio";
|
|
28
|
+
|
|
29
|
+
await startAdminServer({
|
|
30
|
+
storageDirectory: "main",
|
|
31
|
+
storageTarget: new HttpStorageTarget({
|
|
32
|
+
baseUrl: "https://storage.example.com/graphvault",
|
|
33
|
+
headers: { authorization: `Bearer ${process.env.STORAGE_TOKEN}` },
|
|
34
|
+
}),
|
|
35
|
+
port: 4177,
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
SQL-backed storage uses an adapter around your database client:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { SqlStorageTarget } from "@sprengmeister/graphvault";
|
|
43
|
+
import { startAdminServer } from "graphvault-studio";
|
|
44
|
+
|
|
45
|
+
await startAdminServer({
|
|
46
|
+
storageDirectory: "main",
|
|
47
|
+
storageTarget: new SqlStorageTarget({
|
|
48
|
+
client: sqlClientAdapter,
|
|
49
|
+
tableName: "graphvault_objects",
|
|
50
|
+
lockTableName: "graphvault_locks",
|
|
51
|
+
}),
|
|
52
|
+
port: 4177,
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
For mutation endpoints, always set `allowMutations: true` and a `mutationConfirmToken`. For exposed or shared environments, also set `authToken`.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { rm } from "node:fs/promises";
|
|
2
|
+
import { EmbeddedStorage } from "@sprengmeister/graphvault";
|
|
3
|
+
|
|
4
|
+
class Workspace {
|
|
5
|
+
constructor(name) {
|
|
6
|
+
this.name = name;
|
|
7
|
+
this.documents = [];
|
|
8
|
+
this.owners = [];
|
|
9
|
+
this.categories = [];
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class Owner {
|
|
14
|
+
constructor(id, name, region) {
|
|
15
|
+
this.id = id;
|
|
16
|
+
this.name = name;
|
|
17
|
+
this.region = region;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class Category {
|
|
22
|
+
constructor(slug, label) {
|
|
23
|
+
this.slug = slug;
|
|
24
|
+
this.label = label;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class Document {
|
|
29
|
+
constructor(id, title, owner, category, status, views) {
|
|
30
|
+
this.id = id;
|
|
31
|
+
this.title = title;
|
|
32
|
+
this.owner = owner;
|
|
33
|
+
this.category = category;
|
|
34
|
+
this.status = status;
|
|
35
|
+
this.views = views;
|
|
36
|
+
this.tags = new Set();
|
|
37
|
+
this.related = [];
|
|
38
|
+
this.metrics = { score: views / 100, revisions: Math.max(1, Math.round(views / 30)) };
|
|
39
|
+
this.createdAt = new Date(1_765_000_000_000 + views * 60_000);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const storageDirectory = process.argv[2] ?? "./graphvault-studio-demo-store";
|
|
44
|
+
await rm(storageDirectory, { recursive: true, force: true });
|
|
45
|
+
|
|
46
|
+
const root = new Workspace("GraphVault product docs");
|
|
47
|
+
root.owners.push(new Owner("owner-platform", "Platform Team", "EU"));
|
|
48
|
+
root.owners.push(new Owner("owner-tools", "Developer Tools", "US"));
|
|
49
|
+
root.categories.push(new Category("architecture", "Architecture"));
|
|
50
|
+
root.categories.push(new Category("operations", "Operations"));
|
|
51
|
+
root.categories.push(new Category("admin", "Admin tooling"));
|
|
52
|
+
|
|
53
|
+
for (let index = 0; index < 36; index++) {
|
|
54
|
+
const owner = root.owners[index % root.owners.length];
|
|
55
|
+
const category = root.categories[index % root.categories.length];
|
|
56
|
+
const status = index % 11 === 0 ? "archived" : index % 5 === 0 ? "review" : "published";
|
|
57
|
+
const document = new Document(`doc-${String(index + 1).padStart(3, "0")}`, `GraphVault guide ${index + 1}`, owner, category, status, 20 + index * 17);
|
|
58
|
+
document.tags.add(index % 2 === 0 ? "typescript" : "storage");
|
|
59
|
+
document.tags.add(category.slug);
|
|
60
|
+
root.documents.push(document);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (let index = 1; index < root.documents.length; index++) {
|
|
64
|
+
root.documents[index].related.push(root.documents[index - 1]);
|
|
65
|
+
if (index > 4 && index % 4 === 0) {
|
|
66
|
+
root.documents[index].related.push(root.documents[index - 4]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const storage = await EmbeddedStorage.start({
|
|
71
|
+
storageDirectory,
|
|
72
|
+
root,
|
|
73
|
+
types: [
|
|
74
|
+
{ name: "Workspace", ctor: Workspace },
|
|
75
|
+
{ name: "Owner", ctor: Owner },
|
|
76
|
+
{ name: "Category", ctor: Category },
|
|
77
|
+
{ name: "Document", ctor: Document },
|
|
78
|
+
],
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
await storage.storeRoot();
|
|
82
|
+
await storage.shutdown();
|
|
83
|
+
|
|
84
|
+
console.log(`Demo store created at ${storageDirectory}`);
|
|
85
|
+
console.log("Run: npx graphvault-studio --dir " + storageDirectory + " --port 4177 --allow-mutations --confirm-token confirm");
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "graphvault-studio",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Graphical admin client for GraphVault object graph stores.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/Sprengmeister-dev/graphvault-studio.git"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/Sprengmeister-dev/graphvault-studio#readme",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/Sprengmeister-dev/graphvault-studio/issues"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/admin.js",
|
|
18
|
+
"types": "./dist/admin.d.ts",
|
|
19
|
+
"bin": {
|
|
20
|
+
"graphvault-studio": "./dist/admin-cli.js"
|
|
21
|
+
},
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/admin.d.ts",
|
|
25
|
+
"import": "./dist/admin.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"assets",
|
|
30
|
+
"CHANGELOG.md",
|
|
31
|
+
"CONTRIBUTING.md",
|
|
32
|
+
"docs",
|
|
33
|
+
"dist",
|
|
34
|
+
"examples",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -p tsconfig.json",
|
|
40
|
+
"test": "npm run build && node tests/smoke.mjs",
|
|
41
|
+
"demo:store": "npm run build && node examples/create-demo-store.mjs",
|
|
42
|
+
"pack:dry-run": "npm pack --dry-run --cache ./.npm-cache",
|
|
43
|
+
"prepack": "npm run build"
|
|
44
|
+
},
|
|
45
|
+
"keywords": [
|
|
46
|
+
"graphvault",
|
|
47
|
+
"admin-ui",
|
|
48
|
+
"database-admin",
|
|
49
|
+
"graph-database",
|
|
50
|
+
"object-graph",
|
|
51
|
+
"storage-browser",
|
|
52
|
+
"typescript",
|
|
53
|
+
"gvql",
|
|
54
|
+
"developer-tools"
|
|
55
|
+
],
|
|
56
|
+
"license": "MIT",
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=20"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@sprengmeister/graphvault": "0.1.0"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@types/node": "^22.15.3",
|
|
65
|
+
"typescript": "^5.8.3"
|
|
66
|
+
}
|
|
67
|
+
}
|