@kubun/server 0.1.0
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/LICENSE.md +57 -0
- package/README.md +11 -0
- package/lib/data/cursor.d.ts +8 -0
- package/lib/data/cursor.d.ts.map +1 -0
- package/lib/data/cursor.js +7 -0
- package/lib/data/db.d.ts +31 -0
- package/lib/data/db.d.ts.map +1 -0
- package/lib/data/db.js +192 -0
- package/lib/data/engines/engine.d.ts +6 -0
- package/lib/data/engines/engine.d.ts.map +1 -0
- package/lib/data/engines/engine.js +12 -0
- package/lib/data/engines/postgres.d.ts +25 -0
- package/lib/data/engines/postgres.d.ts.map +1 -0
- package/lib/data/engines/postgres.js +30 -0
- package/lib/data/engines/sqlite.d.ts +24 -0
- package/lib/data/engines/sqlite.d.ts.map +1 -0
- package/lib/data/engines/sqlite.js +29 -0
- package/lib/data/engines/types.d.ts +19 -0
- package/lib/data/engines/types.d.ts.map +1 -0
- package/lib/data/engines/types.js +1 -0
- package/lib/data/graphql.d.ts +24 -0
- package/lib/data/graphql.d.ts.map +1 -0
- package/lib/data/graphql.js +106 -0
- package/lib/data/migrations/0-init.d.ts +4 -0
- package/lib/data/migrations/0-init.d.ts.map +1 -0
- package/lib/data/migrations/0-init.js +22 -0
- package/lib/data/migrations/migrations.d.ts +4 -0
- package/lib/data/migrations/migrations.d.ts.map +1 -0
- package/lib/data/migrations/migrations.js +6 -0
- package/lib/data/mutations.d.ts +4 -0
- package/lib/data/mutations.d.ts.map +1 -0
- package/lib/data/mutations.js +66 -0
- package/lib/data/query-builder.d.ts +12 -0
- package/lib/data/query-builder.d.ts.map +1 -0
- package/lib/data/query-builder.js +218 -0
- package/lib/data/types.d.ts +134 -0
- package/lib/data/types.d.ts.map +1 -0
- package/lib/data/types.js +1 -0
- package/lib/handlers/graph.d.ts +8 -0
- package/lib/handlers/graph.d.ts.map +1 -0
- package/lib/handlers/graph.js +174 -0
- package/lib/handlers/index.d.ts +9 -0
- package/lib/handlers/index.d.ts.map +1 -0
- package/lib/handlers/index.js +4 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +2 -0
- package/lib/server.d.ts +18 -0
- package/lib/server.d.ts.map +1 -0
- package/lib/server.js +45 -0
- package/package.json +49 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { AttachmentID, DocumentID } from '@kubun/id';
|
|
2
|
+
import { GraphModel } from '@kubun/protocol';
|
|
3
|
+
import { Kind, OperationTypeNode, execute, parse } from 'graphql';
|
|
4
|
+
import { fromB64 } from '@enkaku/codec';
|
|
5
|
+
import { ServerSchemaBuilder } from '../data/graphql.js';
|
|
6
|
+
import { applyMutation } from '../data/mutations.js';
|
|
7
|
+
export function createHandlers(handlersParams) {
|
|
8
|
+
const { db } = handlersParams;
|
|
9
|
+
const graphModels = {};
|
|
10
|
+
async function getGraphModels(id) {
|
|
11
|
+
if (graphModels[id] == null) {
|
|
12
|
+
graphModels[id] = db.getGraph(id);
|
|
13
|
+
}
|
|
14
|
+
return await graphModels[id];
|
|
15
|
+
}
|
|
16
|
+
const schemas = {};
|
|
17
|
+
async function getGraphQLSchema(id) {
|
|
18
|
+
if (schemas[id] == null) {
|
|
19
|
+
schemas[id] = getGraphModels(id).then((record)=>{
|
|
20
|
+
const schema = new ServerSchemaBuilder({
|
|
21
|
+
record
|
|
22
|
+
}).build();
|
|
23
|
+
// console.log(printSchema(schema))
|
|
24
|
+
return schema;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return await schemas[id];
|
|
28
|
+
}
|
|
29
|
+
function createContext(contextParams) {
|
|
30
|
+
async function createDocument(modelID, data) {
|
|
31
|
+
throw new Error('Not supported');
|
|
32
|
+
}
|
|
33
|
+
async function loadDocument(id) {
|
|
34
|
+
return await db.getDocument(typeof id === 'string' ? DocumentID.fromString(id) : id);
|
|
35
|
+
}
|
|
36
|
+
async function listDocuments(params) {
|
|
37
|
+
const graphModels = await getGraphModels(contextParams.schemaID);
|
|
38
|
+
const models = new Set();
|
|
39
|
+
if (params.model === null) {
|
|
40
|
+
// Match all concrete models
|
|
41
|
+
for (const [id, model] of Object.entries(graphModels)){
|
|
42
|
+
if (model.behavior !== 'interface') {
|
|
43
|
+
models.add(id);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
const matchModel = graphModels[params.model];
|
|
48
|
+
if (matchModel == null) {
|
|
49
|
+
// Model does not exist in graph
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
if (matchModel.behavior === 'interface') {
|
|
53
|
+
// Match all concrete models for the given interface
|
|
54
|
+
for (const [id, model] of Object.entries(graphModels)){
|
|
55
|
+
if (model.interfaces.includes(params.model)) {
|
|
56
|
+
models.add(id);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
// Matches a concrete model
|
|
61
|
+
models.add(params.model);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return await db.listDocuments({
|
|
65
|
+
modelIDs: Array.from(models),
|
|
66
|
+
docIDS: params.ids
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
// TODO: access control filtering
|
|
70
|
+
async function queryDocuments(params) {
|
|
71
|
+
const { entries, hasMore } = await db.queryDocuments(params);
|
|
72
|
+
return {
|
|
73
|
+
edges: entries.map((e)=>({
|
|
74
|
+
cursor: e.cursor,
|
|
75
|
+
node: e.document
|
|
76
|
+
})),
|
|
77
|
+
pageInfo: {
|
|
78
|
+
hasNextPage: params.first ? hasMore : false,
|
|
79
|
+
hasPreviousPage: params.first ? false : hasMore,
|
|
80
|
+
endCursor: entries.at(-1)?.cursor ?? null,
|
|
81
|
+
startCursor: entries.at(0)?.cursor ?? null
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
createDocument,
|
|
87
|
+
listDocuments,
|
|
88
|
+
loadDocument,
|
|
89
|
+
queryDocuments,
|
|
90
|
+
mutatedDocuments: contextParams.mutatedDocuments,
|
|
91
|
+
viewerDID: contextParams.viewerDID ?? null
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
async function executeGraphQL(params) {
|
|
95
|
+
const schema = await getGraphQLSchema(params.schemaID);
|
|
96
|
+
const document = parse(params.text);
|
|
97
|
+
const definition = document.definitions[0];
|
|
98
|
+
if (definition == null) {
|
|
99
|
+
throw new Error('Missing GraphQL document definition');
|
|
100
|
+
}
|
|
101
|
+
// Ensure the operation matching the expected type
|
|
102
|
+
if (definition.kind !== Kind.OPERATION_DEFINITION || definition.operation !== params.type) {
|
|
103
|
+
throw new Error(`Invalid GraphQL document definition: expected ${params.type} operation`);
|
|
104
|
+
}
|
|
105
|
+
const executionResult = await execute({
|
|
106
|
+
document,
|
|
107
|
+
schema,
|
|
108
|
+
variableValues: params.variables,
|
|
109
|
+
contextValue: createContext(params)
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
data: executionResult.data,
|
|
113
|
+
errors: executionResult.errors?.map((err)=>err.toJSON()),
|
|
114
|
+
extensions: executionResult.extensions
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
'graph/create': async (ctx)=>{
|
|
119
|
+
const model = GraphModel.fromClusters({
|
|
120
|
+
clusters: ctx.params.clusters
|
|
121
|
+
});
|
|
122
|
+
const id = await db.createGraph({
|
|
123
|
+
name: ctx.params.name,
|
|
124
|
+
record: model.record
|
|
125
|
+
});
|
|
126
|
+
return {
|
|
127
|
+
id,
|
|
128
|
+
models: model.record
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
'graph/load': async (ctx)=>{
|
|
132
|
+
return {
|
|
133
|
+
models: await getGraphModels(ctx.params.id)
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
'graph/query': async (ctx)=>{
|
|
137
|
+
// TODO(enkaku): request should be JWT:
|
|
138
|
+
// schema ID is token subject
|
|
139
|
+
// viewer ID is token audience or fallback to the issuer
|
|
140
|
+
// also add token capabilities check
|
|
141
|
+
return await executeGraphQL({
|
|
142
|
+
type: OperationTypeNode.QUERY,
|
|
143
|
+
schemaID: ctx.params.id,
|
|
144
|
+
text: ctx.params.text,
|
|
145
|
+
variables: ctx.params.variables
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
'graph/mutate': async (ctx)=>{
|
|
149
|
+
const attachments = Object.entries(ctx.params.attachments ?? {}).map(([key, value])=>{
|
|
150
|
+
const aid = AttachmentID.fromString(key);
|
|
151
|
+
return {
|
|
152
|
+
id: aid.cid.toString(),
|
|
153
|
+
data: fromB64(value)
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
if (attachments.length !== 0) {
|
|
157
|
+
await db.addAttachments(attachments);
|
|
158
|
+
}
|
|
159
|
+
// Apply mutations and make the documents available to the GraphQL resolvers in the context
|
|
160
|
+
const mutatedDocuments = {};
|
|
161
|
+
await Promise.all(Object.entries(ctx.params.mutations).map(async ([key, mutation])=>{
|
|
162
|
+
mutatedDocuments[key] = await applyMutation(db, mutation);
|
|
163
|
+
}));
|
|
164
|
+
return await executeGraphQL({
|
|
165
|
+
type: OperationTypeNode.MUTATION,
|
|
166
|
+
schemaID: ctx.params.id,
|
|
167
|
+
text: ctx.params.text,
|
|
168
|
+
variables: ctx.params.variables,
|
|
169
|
+
// viewerDID: input.invocation.issuer.did(),
|
|
170
|
+
mutatedDocuments
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CommandHandlers } from '@enkaku/server';
|
|
2
|
+
import type { GraphCommands } from '@kubun/protocol';
|
|
3
|
+
import type { KubunDB } from '../data/db.js';
|
|
4
|
+
export type CreateHandlersParams = {
|
|
5
|
+
db: KubunDB;
|
|
6
|
+
};
|
|
7
|
+
export type Handlers = CommandHandlers<GraphCommands>;
|
|
8
|
+
export declare function createHandlers(params: CreateHandlersParams): Handlers;
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAI5C,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,EAAE,OAAO,CAAA;CACZ,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG,eAAe,CAAC,aAAa,CAAC,CAAA;AAErD,wBAAgB,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,QAAQ,CAErE"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AAC1D,OAAO,EAAE,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA"}
|
package/lib/index.js
ADDED
package/lib/server.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Signer } from '@enkaku/jwt';
|
|
2
|
+
import { type Server } from '@enkaku/server';
|
|
3
|
+
import { KubunClient } from '@kubun/client';
|
|
4
|
+
import type { ServerTransport } from '@kubun/protocol';
|
|
5
|
+
import { KubunDB, type KubunDBParams } from './data/db.js';
|
|
6
|
+
export type ServerParams = {
|
|
7
|
+
db: KubunDB | KubunDBParams;
|
|
8
|
+
signer: Signer;
|
|
9
|
+
};
|
|
10
|
+
export declare class KubunServer {
|
|
11
|
+
#private;
|
|
12
|
+
constructor(params: ServerParams);
|
|
13
|
+
get client(): KubunClient;
|
|
14
|
+
get db(): KubunDB;
|
|
15
|
+
createClient(signer: Signer, signal?: AbortSignal): KubunClient;
|
|
16
|
+
serve(transport: ServerTransport, signal?: AbortSignal): Server;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,KAAK,MAAM,EAAS,MAAM,gBAAgB,CAAA;AAEnD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,KAAK,EAAgC,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEpF,OAAO,EAAE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AAG1D,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,OAAO,GAAG,aAAa,CAAA;IAC3B,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED,qBAAa,WAAW;;gBAMV,MAAM,EAAE,YAAY;IAOhC,IAAI,MAAM,IAAI,WAAW,CAKxB;IAED,IAAI,EAAE,IAAI,OAAO,CAEhB;IAED,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,WAAW;IAM/D,KAAK,CAAC,SAAS,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,MAAM;CAGhE"}
|
package/lib/server.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { serve } from '@enkaku/server';
|
|
2
|
+
import { createDirectTransports } from '@enkaku/transport';
|
|
3
|
+
import { KubunClient } from '@kubun/client';
|
|
4
|
+
import { KubunDB } from './data/db.js';
|
|
5
|
+
import { createHandlers } from './handlers/index.js';
|
|
6
|
+
export class KubunServer {
|
|
7
|
+
#client;
|
|
8
|
+
#db;
|
|
9
|
+
#handlers;
|
|
10
|
+
#signer;
|
|
11
|
+
constructor(params){
|
|
12
|
+
const { db, signer } = params;
|
|
13
|
+
this.#db = db instanceof KubunDB ? db : new KubunDB(db);
|
|
14
|
+
this.#handlers = createHandlers({
|
|
15
|
+
db: this.#db
|
|
16
|
+
});
|
|
17
|
+
this.#signer = signer;
|
|
18
|
+
}
|
|
19
|
+
get client() {
|
|
20
|
+
if (this.#client == null) {
|
|
21
|
+
this.#client = this.createClient(this.#signer);
|
|
22
|
+
}
|
|
23
|
+
return this.#client;
|
|
24
|
+
}
|
|
25
|
+
get db() {
|
|
26
|
+
return this.#db;
|
|
27
|
+
}
|
|
28
|
+
createClient(signer, signal) {
|
|
29
|
+
const transports = createDirectTransports({
|
|
30
|
+
signal
|
|
31
|
+
});
|
|
32
|
+
this.serve(transports.server, signal);
|
|
33
|
+
return new KubunClient({
|
|
34
|
+
signer,
|
|
35
|
+
transport: transports.client
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
serve(transport, signal) {
|
|
39
|
+
return serve({
|
|
40
|
+
handlers: this.#handlers,
|
|
41
|
+
signal,
|
|
42
|
+
transport
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kubun/server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "see LICENSE.md",
|
|
5
|
+
"keywords": [],
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "lib/index.js",
|
|
8
|
+
"types": "lib/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./lib/index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"lib/*",
|
|
14
|
+
"LICENSE.md"
|
|
15
|
+
],
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@automerge/automerge": "^2.2.8",
|
|
19
|
+
"@enkaku/codec": "^0.2.0",
|
|
20
|
+
"@enkaku/jwt": "^0.2.3",
|
|
21
|
+
"@enkaku/schema": "^0.2.0",
|
|
22
|
+
"@enkaku/server": "^0.2.1",
|
|
23
|
+
"@enkaku/transport": "^0.2.0",
|
|
24
|
+
"better-sqlite3": "^11.4.0",
|
|
25
|
+
"graphql": "^16.9.0",
|
|
26
|
+
"graphql-relay": "^0.10.2",
|
|
27
|
+
"kysely": "^0.27.4",
|
|
28
|
+
"pg": "^8.13.0",
|
|
29
|
+
"@kubun/client": "^0.1.0",
|
|
30
|
+
"@kubun/id": "^0.1.0",
|
|
31
|
+
"@kubun/protocol": "^0.1.0",
|
|
32
|
+
"@kubun/graphql": "^0.1.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@databases/pg-test": "^3.1.2",
|
|
36
|
+
"@types/better-sqlite3": "^7.6.11",
|
|
37
|
+
"@types/pg": "^8.11.10"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build:clean": "del lib",
|
|
41
|
+
"build:js": "swc src -d ./lib --config-file ../../swc.json --strip-leading-paths",
|
|
42
|
+
"build:types": "tsc --emitDeclarationOnly --skipLibCheck",
|
|
43
|
+
"build:types:ci": "tsc --emitDeclarationOnly --declarationMap false",
|
|
44
|
+
"build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
|
|
45
|
+
"test:types": "tsc --noEmit --skipLibCheck",
|
|
46
|
+
"test:unit": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js",
|
|
47
|
+
"test": "pnpm run test:types && pnpm run test:unit"
|
|
48
|
+
}
|
|
49
|
+
}
|