@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.
Files changed (51) hide show
  1. package/LICENSE.md +57 -0
  2. package/README.md +11 -0
  3. package/lib/data/cursor.d.ts +8 -0
  4. package/lib/data/cursor.d.ts.map +1 -0
  5. package/lib/data/cursor.js +7 -0
  6. package/lib/data/db.d.ts +31 -0
  7. package/lib/data/db.d.ts.map +1 -0
  8. package/lib/data/db.js +192 -0
  9. package/lib/data/engines/engine.d.ts +6 -0
  10. package/lib/data/engines/engine.d.ts.map +1 -0
  11. package/lib/data/engines/engine.js +12 -0
  12. package/lib/data/engines/postgres.d.ts +25 -0
  13. package/lib/data/engines/postgres.d.ts.map +1 -0
  14. package/lib/data/engines/postgres.js +30 -0
  15. package/lib/data/engines/sqlite.d.ts +24 -0
  16. package/lib/data/engines/sqlite.d.ts.map +1 -0
  17. package/lib/data/engines/sqlite.js +29 -0
  18. package/lib/data/engines/types.d.ts +19 -0
  19. package/lib/data/engines/types.d.ts.map +1 -0
  20. package/lib/data/engines/types.js +1 -0
  21. package/lib/data/graphql.d.ts +24 -0
  22. package/lib/data/graphql.d.ts.map +1 -0
  23. package/lib/data/graphql.js +106 -0
  24. package/lib/data/migrations/0-init.d.ts +4 -0
  25. package/lib/data/migrations/0-init.d.ts.map +1 -0
  26. package/lib/data/migrations/0-init.js +22 -0
  27. package/lib/data/migrations/migrations.d.ts +4 -0
  28. package/lib/data/migrations/migrations.d.ts.map +1 -0
  29. package/lib/data/migrations/migrations.js +6 -0
  30. package/lib/data/mutations.d.ts +4 -0
  31. package/lib/data/mutations.d.ts.map +1 -0
  32. package/lib/data/mutations.js +66 -0
  33. package/lib/data/query-builder.d.ts +12 -0
  34. package/lib/data/query-builder.d.ts.map +1 -0
  35. package/lib/data/query-builder.js +218 -0
  36. package/lib/data/types.d.ts +134 -0
  37. package/lib/data/types.d.ts.map +1 -0
  38. package/lib/data/types.js +1 -0
  39. package/lib/handlers/graph.d.ts +8 -0
  40. package/lib/handlers/graph.d.ts.map +1 -0
  41. package/lib/handlers/graph.js +174 -0
  42. package/lib/handlers/index.d.ts +9 -0
  43. package/lib/handlers/index.d.ts.map +1 -0
  44. package/lib/handlers/index.js +4 -0
  45. package/lib/index.d.ts +3 -0
  46. package/lib/index.d.ts.map +1 -0
  47. package/lib/index.js +2 -0
  48. package/lib/server.d.ts +18 -0
  49. package/lib/server.d.ts.map +1 -0
  50. package/lib/server.js +45 -0
  51. 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"}
@@ -0,0 +1,4 @@
1
+ import { createHandlers as graphHandlers } from './graph.js';
2
+ export function createHandlers(params) {
3
+ return graphHandlers(params);
4
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { KubunDB, type KubunDBParams } from './data/db.js';
2
+ export { KubunServer, type ServerParams } from './server.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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
@@ -0,0 +1,2 @@
1
+ export { KubunDB } from './data/db.js';
2
+ export { KubunServer } from './server.js';
@@ -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
+ }