@kubun/server 0.2.4 → 0.3.1

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.
@@ -1,20 +1,19 @@
1
- import type { Document, DocumentData, ListDocumentsParams, QueryDocumentsParams } from '@kubun/db';
2
- import { SchemaBuilder, type SharedDefinitions } from '@kubun/graphql';
3
- import type { DocumentID } from '@kubun/id';
4
- import { type Connection, type ConnectionArguments } from 'graphql-relay';
5
- export type Context = {
6
- createDocument: (modelID: string, data: DocumentData) => Promise<Document>;
7
- listDocuments: (params: ListDocumentsParams) => Promise<Array<Document>>;
8
- loadDocument: (id: DocumentID | string) => Promise<Document | null>;
9
- queryDocuments: (params: QueryDocumentsParams) => Promise<Connection<Document>>;
1
+ import type { KubunDB } from '@kubun/db';
2
+ import type { Context } from '@kubun/graphql';
3
+ import type { DocumentNode } from '@kubun/protocol';
4
+ import { type ExecutionArgs, type GraphQLSchema, type OperationTypeNode } from 'graphql';
5
+ export type ExecutionContext = {
6
+ db: KubunDB;
10
7
  viewerDID: string;
11
- mutatedDocuments?: Record<string, Document>;
8
+ mutatedDocuments?: Record<string, DocumentNode>;
12
9
  };
13
- export declare class ServerSchemaBuilder extends SchemaBuilder<Document, Context> {
14
- getViewer(ctx: Context): string;
15
- loadDocument(id: string, ctx: Context): Promise<Document | null>;
16
- resolveConnection(model: string, args: ConnectionArguments, ctx: Context): Promise<Connection<Document>>;
17
- resolveList(model: string, docIDs: Array<string>, ctx: Context): Promise<Array<Document>>;
18
- buildMutations(id: string, definitions: SharedDefinitions<Document, Context>): void;
19
- }
10
+ export declare function createContext(ctx: ExecutionContext): Context;
11
+ export type ExecuteGraphQLParams = {
12
+ context: ExecutionContext;
13
+ schema: GraphQLSchema;
14
+ text: string;
15
+ type: OperationTypeNode;
16
+ variables: Record<string, unknown>;
17
+ };
18
+ export declare function getExecutionArgs(params: ExecuteGraphQLParams): ExecutionArgs;
20
19
  //# sourceMappingURL=graphql.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"graphql.d.ts","sourceRoot":"","sources":["../../src/data/graphql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAA;AAClG,OAAO,EAAkB,aAAa,EAAE,KAAK,iBAAiB,EAAU,MAAM,gBAAgB,CAAA;AAC9F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAE3C,OAAO,EACL,KAAK,UAAU,EACf,KAAK,mBAAmB,EAEzB,MAAM,eAAe,CAAA;AAEtB,MAAM,MAAM,OAAO,GAAG;IACpB,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC1E,aAAa,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAA;IACxE,YAAY,EAAE,CAAC,EAAE,EAAE,UAAU,GAAG,MAAM,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAA;IACnE,cAAc,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC/E,SAAS,EAAE,MAAM,CAAA;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;CAC5C,CAAA;AAED,qBAAa,mBAAoB,SAAQ,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC;IACvE,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM;IAIzB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAIhE,iBAAiB,CACrB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,mBAAmB,EACzB,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAI1B,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAI/F,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,iBAAiB,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,IAAI;CAiEpF"}
1
+ {"version":3,"file":"graphql.d.ts","sourceRoot":"","sources":["../../src/data/graphql.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAsB,OAAO,EAAE,MAAM,WAAW,CAAA;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAuB,MAAM,gBAAgB,CAAA;AAElE,OAAO,KAAK,EAAgB,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACjE,OAAO,EACL,KAAK,aAAa,EAElB,KAAK,aAAa,EAElB,KAAK,iBAAiB,EAEvB,MAAM,SAAS,CAAA;AAGhB,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,OAAO,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;CAChD,CAAA;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAsK5D;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,gBAAgB,CAAA;IACzB,MAAM,EAAE,aAAa,CAAA;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,iBAAiB,CAAA;IACvB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC,CAAA;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,oBAAoB,GAAG,aAAa,CAiB5E"}
@@ -1,109 +1,152 @@
1
- import { PatchOperation, SchemaBuilder, toList } from '@kubun/graphql';
2
- import { GraphQLID, GraphQLNonNull } from 'graphql';
3
- import { mutationWithClientMutationId } from 'graphql-relay';
4
- export class ServerSchemaBuilder extends SchemaBuilder {
5
- getViewer(ctx) {
6
- return ctx.viewerDID;
7
- }
8
- async loadDocument(id, ctx) {
9
- return await ctx.loadDocument(id);
10
- }
11
- async resolveConnection(model, args, ctx) {
12
- return await ctx.queryDocuments({
13
- ...args,
14
- modelIDs: this.getModelIDs(model)
15
- });
16
- }
17
- async resolveList(model, docIDs, ctx) {
18
- return await ctx.listDocuments({
19
- modelIDs: this.getModelIDs(model),
20
- docIDs
1
+ import { defer } from '@enkaku/async';
2
+ import { DocumentID } from '@kubun/id';
3
+ import { Kind, parse } from 'graphql';
4
+ export function createContext(ctx) {
5
+ function createEventGenerator(filter, transform) {
6
+ let isDone = false;
7
+ let pending = null;
8
+ const queue = [];
9
+ const stop = ctx.db.events.on('document:saved', (event)=>{
10
+ if (filter(event)) {
11
+ const toPush = transform(event);
12
+ if (pending == null) {
13
+ queue.push(toPush);
14
+ } else {
15
+ pending.resolve(toPush);
16
+ pending = null;
17
+ }
18
+ }
21
19
  });
22
- }
23
- buildMutations(id, definitions) {
24
- const model = this._record[id];
25
- if (model == null) {
26
- throw new Error(`Could not find model id: ${id}`);
27
- }
28
- const name = this._aliases[id];
29
- switch(model.behavior){
30
- case 'interface':
31
- // Interfaces don't have mutations
32
- return;
33
- case 'default':
34
- {
35
- this._mutations[`create${name}`] = mutationWithClientMutationId({
36
- name: `Create${name}`,
37
- inputFields: ()=>({
38
- data: {
39
- type: new GraphQLNonNull(this._inputObjects[id])
40
- }
41
- }),
42
- outputFields: ()=>({
43
- ...definitions.queryFields,
44
- document: {
45
- type: new GraphQLNonNull(this._types[id])
46
- }
47
- }),
48
- mutateAndGetPayload: async (_input, ctx, info)=>{
49
- const document = ctx.mutatedDocuments?.[info.path.key];
50
- return {
51
- document
52
- };
53
- }
20
+ const setDone = ()=>{
21
+ stop();
22
+ isDone = true;
23
+ return Promise.resolve({
24
+ done: true,
25
+ value: undefined
26
+ });
27
+ };
28
+ return {
29
+ [Symbol.asyncIterator] () {
30
+ return this;
31
+ },
32
+ next: ()=>{
33
+ if (isDone) {
34
+ return Promise.resolve({
35
+ done: true,
36
+ value: undefined
54
37
  });
55
- break;
56
38
  }
57
- case 'unique':
58
- {
59
- this._mutations[`set${name}`] = mutationWithClientMutationId({
60
- name: `Set${name}`,
61
- inputFields: ()=>({
62
- data: {
63
- type: new GraphQLNonNull(this._inputObjects[id])
64
- }
65
- }),
66
- outputFields: ()=>({
67
- ...definitions.queryFields,
68
- document: {
69
- type: new GraphQLNonNull(this._types[id])
70
- }
71
- }),
72
- mutateAndGetPayload: async (_input, ctx, info)=>{
73
- const document = ctx.mutatedDocuments?.[info.path.key];
74
- return {
75
- document
76
- };
77
- }
39
+ const value = queue.shift();
40
+ if (value != null) {
41
+ return Promise.resolve({
42
+ value,
43
+ done: false
78
44
  });
79
- break;
80
45
  }
46
+ pending = defer();
47
+ return pending.promise.then((value)=>({
48
+ value,
49
+ done: false
50
+ }));
51
+ },
52
+ return: setDone,
53
+ throw: setDone
54
+ };
55
+ }
56
+ function getMutationDocument(info) {
57
+ return ctx.mutatedDocuments?.[info.path.key];
58
+ }
59
+ return {
60
+ getViewer () {
61
+ return ctx.viewerDID;
62
+ },
63
+ async loadDocument (id) {
64
+ return await ctx.db.getDocument(typeof id === 'string' ? DocumentID.fromString(id) : id);
65
+ },
66
+ async resolveConnection (modelIDs, args) {
67
+ // TODO: access control filtering
68
+ const { entries, hasMore } = await ctx.db.queryDocuments({
69
+ ...args,
70
+ modelIDs
71
+ });
72
+ return {
73
+ edges: entries.map((e)=>({
74
+ cursor: e.cursor,
75
+ node: e.document
76
+ })),
77
+ pageInfo: {
78
+ hasNextPage: args.first ? hasMore : false,
79
+ hasPreviousPage: args.first ? false : hasMore,
80
+ endCursor: entries.at(-1)?.cursor ?? null,
81
+ startCursor: entries.at(0)?.cursor ?? null
82
+ }
83
+ };
84
+ },
85
+ async resolveList (modelIDs, docIDs) {
86
+ return await ctx.db.listDocuments({
87
+ modelIDs,
88
+ docIDs
89
+ });
90
+ },
91
+ subscribeToDocumentCreated (modelIDs) {
92
+ return createEventGenerator((event)=>event.type === 'create' && modelIDs.includes(event.document.model), (event)=>event.document);
93
+ },
94
+ subscribeToDocumentChanged (id) {
95
+ return createEventGenerator((event)=>{
96
+ return event.type === 'update' && event.document.id === id && event.document.data !== null;
97
+ }, (event)=>event.document);
98
+ },
99
+ subscribeToDocumentRemoved () {
100
+ return createEventGenerator((event)=>event.type === 'update' && event.document.data === null, (event)=>event.document.id);
101
+ },
102
+ subscribeToDocumentEdgeAdded (modelIDs, hasRelation) {
103
+ return createEventGenerator((event)=>{
104
+ return(// document has data
105
+ event.document.data !== null && // document model is one of the target models
106
+ modelIDs.includes(event.document.model) && // relation field targets the expected document
107
+ hasRelation(event.document.data) && // new document or new relation to document
108
+ (event.type === 'create' || event.previous.data !== null && hasRelation(event.previous.data)));
109
+ }, (event)=>({
110
+ cursor: event.getCursor(),
111
+ node: event.document
112
+ }));
113
+ },
114
+ subscribeToDocumentEdgeRemoved (modelIDs, hasRelation) {
115
+ return createEventGenerator((event)=>{
116
+ return event.type === 'update' && // document model is one of the target models
117
+ modelIDs.includes(event.document.model) && // relation existed in previous version
118
+ event.previous.data !== null && hasRelation(event.previous.data) && // relation is removed
119
+ (event.document.data === null || !hasRelation(event.document.data));
120
+ }, (event)=>event.document.id);
121
+ },
122
+ async executeCreateMutation (modelID, data, info) {
123
+ return getMutationDocument(info);
124
+ },
125
+ async executeSetMutation (modelID, unique, data, info) {
126
+ return getMutationDocument(info);
127
+ },
128
+ async executeUpdateMutation (input, info) {
129
+ return getMutationDocument(info);
130
+ },
131
+ async executeRemoveMutation (id, info) {
132
+ // no-op
81
133
  }
82
- this._mutations[`update${name}`] = mutationWithClientMutationId({
83
- name: `Update${name}`,
84
- inputFields: ()=>({
85
- id: {
86
- type: new GraphQLNonNull(GraphQLID)
87
- },
88
- patch: {
89
- type: new GraphQLNonNull(toList(PatchOperation))
90
- },
91
- from: {
92
- type: this._inputObjects[`${id}-update`]
93
- }
94
- }),
95
- outputFields: ()=>({
96
- ...definitions.queryFields,
97
- document: {
98
- type: this._types[id]
99
- }
100
- }),
101
- mutateAndGetPayload: async (_input, ctx, info)=>{
102
- const document = ctx.mutatedDocuments?.[info.path.key];
103
- return {
104
- document
105
- };
106
- }
107
- });
134
+ };
135
+ }
136
+ export function getExecutionArgs(params) {
137
+ const document = parse(params.text);
138
+ const definition = document.definitions[0];
139
+ if (definition == null) {
140
+ throw new Error('Missing GraphQL document definition');
141
+ }
142
+ // Ensure the operation matching the expected type
143
+ if (definition.kind !== Kind.OPERATION_DEFINITION || definition.operation !== params.type) {
144
+ throw new Error(`Invalid GraphQL document definition: expected ${params.type} operation`);
108
145
  }
146
+ return {
147
+ document,
148
+ schema: params.schema,
149
+ variableValues: params.variables,
150
+ contextValue: createContext(params.context)
151
+ };
109
152
  }
@@ -1,6 +1,6 @@
1
1
  import { type Validator } from '@enkaku/schema';
2
- import type { Document, KubunDB } from '@kubun/db';
3
- import { type DocumentData } from '@kubun/protocol';
2
+ import type { KubunDB } from '@kubun/db';
3
+ import { type DocumentData, type DocumentNode } from '@kubun/protocol';
4
4
  type DocumentValidator = Validator<DocumentData>;
5
5
  type ValidatorsRecord = Record<string, Promise<DocumentValidator>>;
6
6
  export type MutationContext = {
@@ -8,6 +8,6 @@ export type MutationContext = {
8
8
  validators: ValidatorsRecord;
9
9
  };
10
10
  export declare function getDocumentValidator(ctx: MutationContext, id: string): Promise<DocumentValidator>;
11
- export declare function applyMutation(ctx: MutationContext, token: string): Promise<Document>;
11
+ export declare function applyMutation(ctx: MutationContext, token: string): Promise<DocumentNode>;
12
12
  export {};
13
13
  //# sourceMappingURL=mutations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mutations.d.ts","sourceRoot":"","sources":["../../src/data/mutations.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,SAAS,EAA2B,MAAM,gBAAgB,CAAA;AAExE,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAElD,OAAO,EAAE,KAAK,YAAY,EAA2C,MAAM,iBAAiB,CAAA;AAM5F,KAAK,iBAAiB,GAAG,SAAS,CAAC,YAAY,CAAC,CAAA;AAChD,KAAK,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAA;AAElE,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,OAAO,CAAA;IACX,UAAU,EAAE,gBAAgB,CAAA;CAC7B,CAAA;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAOjG;AAED,wBAAsB,aAAa,CAAC,GAAG,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CA4E1F"}
1
+ {"version":3,"file":"mutations.d.ts","sourceRoot":"","sources":["../../src/data/mutations.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,SAAS,EAA2B,MAAM,gBAAgB,CAAA;AAExE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAExC,OAAO,EACL,KAAK,YAAY,EAEjB,KAAK,YAAY,EAElB,MAAM,iBAAiB,CAAA;AAMxB,KAAK,iBAAiB,GAAG,SAAS,CAAC,YAAY,CAAC,CAAA;AAChD,KAAK,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAA;AAElE,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,OAAO,CAAA;IACX,UAAU,EAAE,gBAAgB,CAAA;CAC7B,CAAA;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAOjG;AAED,wBAAsB,aAAa,CAAC,GAAG,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAiF9F"}
@@ -38,6 +38,7 @@ export async function applyMutation(ctx, token) {
38
38
  if (mutation.data === null) {
39
39
  return await ctx.db.saveDocument({
40
40
  id,
41
+ existing: doc,
41
42
  data: null,
42
43
  state: null
43
44
  });
@@ -60,6 +61,7 @@ export async function applyMutation(ctx, token) {
60
61
  const data = asType(validator, automergeToData(newDoc));
61
62
  return await ctx.db.saveDocument({
62
63
  id,
64
+ existing: doc,
63
65
  data,
64
66
  state: A.save(newDoc)
65
67
  });
@@ -92,6 +94,7 @@ export async function applyMutation(ctx, token) {
92
94
  }
93
95
  return await ctx.db.saveDocument({
94
96
  id,
97
+ existing: doc,
95
98
  data,
96
99
  state: mergeDoc ? A.save(mergeDoc) : null
97
100
  });
@@ -1,6 +1,6 @@
1
1
  import type { ProcedureHandlers } from '@enkaku/server';
2
2
  import type { KubunDB } from '@kubun/db';
3
- import { type GraphProtocol } from '@kubun/protocol';
3
+ import type { GraphProtocol } from '@kubun/protocol';
4
4
  export type CreateHandlersParams = {
5
5
  db: KubunDB;
6
6
  };
@@ -1 +1 @@
1
- {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../src/handlers/graph.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAEvD,OAAO,KAAK,EAIV,OAAO,EAER,MAAM,WAAW,CAAA;AAElB,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAA;AAOxB,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,EAAE,OAAO,CAAA;CACZ,CAAA;AAcD,wBAAgB,cAAc,CAC5B,cAAc,EAAE,oBAAoB,GACnC,iBAAiB,CAAC,aAAa,CAAC,CAyJlC"}
1
+ {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../src/handlers/graph.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAEvD,OAAO,KAAK,EAA4B,OAAO,EAAE,MAAM,WAAW,CAAA;AAGlE,OAAO,KAAK,EAKV,aAAa,EACd,MAAM,iBAAiB,CAAA;AAsBxB,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,EAAE,OAAO,CAAA;CACZ,CAAA;AAED,wBAAgB,cAAc,CAC5B,cAAc,EAAE,oBAAoB,GACnC,iBAAiB,CAAC,aAAa,CAAC,CA4IlC"}
@@ -1,9 +1,18 @@
1
1
  import { fromB64, toB64 } from '@enkaku/codec';
2
- import { AttachmentID, DocumentID } from '@kubun/id';
2
+ import { consume } from '@enkaku/generator';
3
+ import { createSchema } from '@kubun/graphql';
4
+ import { AttachmentID } from '@kubun/id';
3
5
  import { GraphModel } from '@kubun/protocol';
4
- import { Kind, OperationTypeNode, execute, parse } from 'graphql';
5
- import { ServerSchemaBuilder } from '../data/graphql.js';
6
+ import { OperationTypeNode, execute, subscribe } from 'graphql';
7
+ import { getExecutionArgs } from '../data/graphql.js';
6
8
  import { applyMutation } from '../data/mutations.js';
9
+ function toGraphResult(result) {
10
+ return {
11
+ data: result.data,
12
+ errors: result.errors?.map((err)=>err.toJSON()),
13
+ extensions: result.extensions
14
+ };
15
+ }
7
16
  export function createHandlers(handlersParams) {
8
17
  const { db } = handlersParams;
9
18
  const graphModels = {};
@@ -22,69 +31,16 @@ export function createHandlers(handlersParams) {
22
31
  async function getGraphQLSchema(id) {
23
32
  if (schemas[id] == null) {
24
33
  schemas[id] = getGraphModels(id).then((record)=>{
25
- const schema = new ServerSchemaBuilder({
26
- record
27
- }).build();
34
+ const schema = createSchema(record);
28
35
  // console.log(printSchema(schema))
29
36
  return schema;
30
37
  });
31
38
  }
32
39
  return await schemas[id];
33
40
  }
34
- function createContext(contextParams) {
35
- async function createDocument(modelID, data) {
36
- throw new Error('Not supported');
37
- }
38
- async function loadDocument(id) {
39
- return await db.getDocument(typeof id === 'string' ? DocumentID.fromString(id) : id);
40
- }
41
- // TODO: access control filtering
42
- async function queryDocuments(params) {
43
- const { entries, hasMore } = await db.queryDocuments(params);
44
- return {
45
- edges: entries.map((e)=>({
46
- cursor: e.cursor,
47
- node: e.document
48
- })),
49
- pageInfo: {
50
- hasNextPage: params.first ? hasMore : false,
51
- hasPreviousPage: params.first ? false : hasMore,
52
- endCursor: entries.at(-1)?.cursor ?? null,
53
- startCursor: entries.at(0)?.cursor ?? null
54
- }
55
- };
56
- }
57
- return {
58
- createDocument,
59
- listDocuments: async (params)=>await db.listDocuments(params),
60
- loadDocument,
61
- queryDocuments,
62
- mutatedDocuments: contextParams.mutatedDocuments,
63
- viewerDID: contextParams.viewerDID
64
- };
65
- }
66
41
  async function executeGraphQL(params) {
67
- const schema = await getGraphQLSchema(params.schemaID);
68
- const document = parse(params.text);
69
- const definition = document.definitions[0];
70
- if (definition == null) {
71
- throw new Error('Missing GraphQL document definition');
72
- }
73
- // Ensure the operation matching the expected type
74
- if (definition.kind !== Kind.OPERATION_DEFINITION || definition.operation !== params.type) {
75
- throw new Error(`Invalid GraphQL document definition: expected ${params.type} operation`);
76
- }
77
- const executionResult = await execute({
78
- document,
79
- schema,
80
- variableValues: params.variables,
81
- contextValue: createContext(params)
82
- });
83
- return {
84
- data: executionResult.data,
85
- errors: executionResult.errors?.map((err)=>err.toJSON()),
86
- extensions: executionResult.extensions
87
- };
42
+ const result = await execute(getExecutionArgs(params));
43
+ return toGraphResult(result);
88
44
  }
89
45
  return {
90
46
  'graph/deploy': async (ctx)=>{
@@ -115,19 +71,6 @@ export function createHandlers(handlersParams) {
115
71
  models: await getGraphModels(ctx.param.id)
116
72
  };
117
73
  },
118
- 'graph/query': async (ctx)=>{
119
- const payload = ctx.message.payload;
120
- // TODO: viewerDID is token subject or fallback to the issuer
121
- // also add token capabilities check
122
- const viewerDID = payload.iss;
123
- return await executeGraphQL({
124
- type: OperationTypeNode.QUERY,
125
- schemaID: ctx.param.id,
126
- text: ctx.param.text,
127
- variables: ctx.param.variables ?? {},
128
- viewerDID
129
- });
130
- },
131
74
  'graph/mutate': async (ctx)=>{
132
75
  const attachments = Object.entries(ctx.param.attachments ?? {}).map(([key, value])=>{
133
76
  const aid = AttachmentID.fromString(key);
@@ -153,13 +96,66 @@ export function createHandlers(handlersParams) {
153
96
  // also add token capabilities check
154
97
  const viewerDID = payload.iss;
155
98
  return await executeGraphQL({
99
+ schema: await getGraphQLSchema(ctx.param.id),
156
100
  type: OperationTypeNode.MUTATION,
157
- schemaID: ctx.param.id,
158
101
  text: ctx.param.text,
159
102
  variables: ctx.param.variables ?? {},
160
- mutatedDocuments,
161
- viewerDID
103
+ context: {
104
+ db,
105
+ mutatedDocuments,
106
+ viewerDID
107
+ }
108
+ });
109
+ },
110
+ 'graph/query': async (ctx)=>{
111
+ const payload = ctx.message.payload;
112
+ // TODO: viewerDID is token subject or fallback to the issuer
113
+ // also add token capabilities check
114
+ const viewerDID = payload.iss;
115
+ return await executeGraphQL({
116
+ schema: await getGraphQLSchema(ctx.param.id),
117
+ type: OperationTypeNode.QUERY,
118
+ text: ctx.param.text,
119
+ variables: ctx.param.variables ?? {},
120
+ context: {
121
+ db,
122
+ viewerDID
123
+ }
162
124
  });
125
+ },
126
+ 'graph/subscribe': async (ctx)=>{
127
+ const payload = ctx.message.payload;
128
+ const viewerDID = payload.iss;
129
+ const args = getExecutionArgs({
130
+ schema: await getGraphQLSchema(ctx.param.id),
131
+ type: OperationTypeNode.SUBSCRIPTION,
132
+ text: ctx.param.text,
133
+ variables: ctx.param.variables ?? {},
134
+ context: {
135
+ db,
136
+ viewerDID
137
+ }
138
+ });
139
+ const subscription = await subscribe(args);
140
+ if (ctx.signal.aborted) {
141
+ return null;
142
+ }
143
+ if ('errors' in subscription) {
144
+ return toGraphResult(subscription);
145
+ }
146
+ const writer = ctx.writable.getWriter();
147
+ try {
148
+ await consume(subscription, async (value)=>{
149
+ await writer.write(toGraphResult(value));
150
+ }, ctx.signal);
151
+ } catch (reason) {
152
+ if (reason !== 'Close') {
153
+ throw reason;
154
+ }
155
+ } finally{
156
+ await writer.close();
157
+ }
158
+ return null;
163
159
  }
164
160
  };
165
161
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubun/server",
3
- "version": "0.2.4",
3
+ "version": "0.3.1",
4
4
  "license": "see LICENSE.md",
5
5
  "keywords": [],
6
6
  "type": "module",
@@ -16,24 +16,26 @@
16
16
  "sideEffects": false,
17
17
  "dependencies": {
18
18
  "@automerge/automerge": "^2.2.8",
19
- "@enkaku/async": "^0.11.0",
20
- "@enkaku/codec": "^0.11.0",
21
- "@enkaku/schema": "^0.11.1",
22
- "@enkaku/server": "^0.11.0",
23
- "@enkaku/token": "^0.11.0",
24
- "@enkaku/transport": "^0.11.0",
19
+ "@enkaku/async": "^0.12.0",
20
+ "@enkaku/codec": "^0.12.0",
21
+ "@enkaku/generator": "^0.12.0",
22
+ "@enkaku/schema": "^0.12.0",
23
+ "@enkaku/server": "^0.12.0",
24
+ "@enkaku/token": "^0.12.0",
25
+ "@enkaku/transport": "^0.12.0",
25
26
  "graphql": "^16.9.0",
26
- "graphql-relay": "^0.10.2",
27
- "@kubun/graphql": "^0.2.5",
28
- "@kubun/client": "^0.2.3",
29
- "@kubun/db": "^0.2.2",
30
- "@kubun/protocol": "^0.2.5",
31
- "@kubun/id": "^0.2.0"
27
+ "@kubun/client": "^0.3.1",
28
+ "@kubun/graphql": "^0.3.1",
29
+ "@kubun/protocol": "^0.3.1",
30
+ "@kubun/id": "^0.3.0",
31
+ "@kubun/db": "^0.3.1"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@databases/pg-test": "^3.1.2",
35
- "@kubun/db-postgres": "^0.2.0",
36
- "@kubun/db-sqlite": "^0.2.0"
35
+ "@enkaku/stream": "^0.12.0",
36
+ "graphql-relay": "^0.10.2",
37
+ "@kubun/db-postgres": "^0.3.1",
38
+ "@kubun/db-sqlite": "^0.3.1"
37
39
  },
38
40
  "scripts": {
39
41
  "build:clean": "del lib",