@kubun/server 0.3.2 → 0.3.4

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,5 +1,5 @@
1
1
  import type { KubunDB } from '@kubun/db';
2
- import type { Context } from '@kubun/graphql';
2
+ import { type Context } from '@kubun/graphql';
3
3
  import type { DocumentNode } from '@kubun/protocol';
4
4
  import { type ExecutionArgs, type GraphQLSchema, type OperationTypeNode } from 'graphql';
5
5
  export type ExecutionContext = {
@@ -1 +1 @@
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
+ {"version":3,"file":"graphql.d.ts","sourceRoot":"","sources":["../../src/data/graphql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAE,KAAK,OAAO,EAA+C,MAAM,gBAAgB,CAAA;AAC1F,OAAO,KAAK,EAAgB,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACjE,OAAO,EACL,KAAK,aAAa,EAElB,KAAK,aAAa,EAElB,KAAK,iBAAiB,EAEvB,MAAM,SAAS,CAAA;AAEhB,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,CAkC5D;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,124 +1,12 @@
1
- import { defer } from '@enkaku/async';
2
- import { DocumentID } from '@kubun/id';
1
+ import { createReadContext } from '@kubun/graphql';
3
2
  import { Kind, parse } from 'graphql';
4
3
  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
- }
19
- });
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
37
- });
38
- }
39
- const value = queue.shift();
40
- if (value != null) {
41
- return Promise.resolve({
42
- value,
43
- done: false
44
- });
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
- }
4
+ const readContext = createReadContext(ctx);
56
5
  function getMutationDocument(info) {
57
6
  return ctx.mutatedDocuments?.[info.path.key];
58
7
  }
59
8
  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
- },
9
+ ...readContext,
122
10
  async executeCreateMutation (modelID, data, info) {
123
11
  return getMutationDocument(info);
124
12
  },
@@ -1,13 +1,4 @@
1
- import { type Validator } from '@enkaku/schema';
2
- import type { KubunDB } from '@kubun/db';
3
- import { type DocumentData, type DocumentNode } from '@kubun/protocol';
4
- type DocumentValidator = Validator<DocumentData>;
5
- type ValidatorsRecord = Record<string, Promise<DocumentValidator>>;
6
- export type MutationContext = {
7
- db: KubunDB;
8
- validators: ValidatorsRecord;
9
- };
10
- export declare function getDocumentValidator(ctx: MutationContext, id: string): Promise<DocumentValidator>;
1
+ import { type MutationContext } from '@kubun/mutation';
2
+ import { type DocumentNode } from '@kubun/protocol';
11
3
  export declare function applyMutation(ctx: MutationContext, token: string): Promise<DocumentNode>;
12
- export {};
13
4
  //# 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,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"}
1
+ {"version":3,"file":"mutations.d.ts","sourceRoot":"","sources":["../../src/data/mutations.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,eAAe,EAA0B,MAAM,iBAAiB,CAAA;AAC9E,OAAO,EAAyB,KAAK,YAAY,EAAoB,MAAM,iBAAiB,CAAA;AAI5F,wBAAsB,aAAa,CAAC,GAAG,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAI9F"}
@@ -1,105 +1,10 @@
1
- // @ts-ignore missing type definition
2
- import * as A from '@automerge/automerge/slim';
3
- import { fromB64 } from '@enkaku/codec';
4
1
  import { asType, createValidator } from '@enkaku/schema';
5
2
  import { verifyToken } from '@enkaku/token';
6
- import { DocumentID } from '@kubun/id';
3
+ import { applyMutation as apply } from '@kubun/mutation';
7
4
  import { documentMutation } from '@kubun/protocol';
8
- import { automergeReady, automergeToData } from './automerge.js';
9
5
  const validateMutation = createValidator(documentMutation);
10
- export function getDocumentValidator(ctx, id) {
11
- if (ctx.validators[id] == null) {
12
- ctx.validators[id] = ctx.db.getDocumentModel(id).then((model)=>createValidator({
13
- ...model.schema,
14
- $id: id
15
- }));
16
- }
17
- return ctx.validators[id];
18
- }
19
6
  export async function applyMutation(ctx, token) {
20
- const [verified] = await Promise.all([
21
- verifyToken(token),
22
- automergeReady
23
- ]);
7
+ const verified = await verifyToken(token);
24
8
  const mutation = asType(validateMutation, verified.payload);
25
- switch(mutation.typ){
26
- case 'change':
27
- {
28
- const id = DocumentID.fromString(mutation.sub);
29
- const docID = id.toString();
30
- const doc = await ctx.db.getDocument(id);
31
- if (doc == null) {
32
- throw new Error(`Document not found: ${docID}`);
33
- }
34
- if (mutation.iss !== doc.owner) {
35
- // TODO: verify capabilities if issuer is not owner
36
- throw new Error('Invalid mutation issuer');
37
- }
38
- if (mutation.data === null) {
39
- return await ctx.db.saveDocument({
40
- id,
41
- existing: doc,
42
- data: null,
43
- state: null
44
- });
45
- }
46
- if (doc.data === null) {
47
- throw new Error(`Cannot apply changes to empty document: ${docID}`);
48
- }
49
- const [docStates, validator] = await Promise.all([
50
- ctx.db.getDocumentStates([
51
- docID
52
- ]),
53
- getDocumentValidator(ctx, id.model.toString())
54
- ]);
55
- // Create doc from data object if state is not present
56
- const currentDoc = docStates[docID] ? A.load(docStates[docID]) : A.from(doc.data);
57
- const changes = fromB64(mutation.data);
58
- // Apply incremental changes or full merge
59
- const newDoc = mutation.inc ? A.loadIncremental(currentDoc, changes) : A.merge(currentDoc, A.load(changes));
60
- // Validate merged data
61
- const data = asType(validator, automergeToData(newDoc));
62
- return await ctx.db.saveDocument({
63
- id,
64
- existing: doc,
65
- data,
66
- state: A.save(newDoc)
67
- });
68
- }
69
- case 'set':
70
- {
71
- const owner = mutation.aud ?? mutation.iss;
72
- if (mutation.iss !== owner) {
73
- // TODO: verify capabilities if issuer is not owner
74
- throw new Error('Invalid mutation issuer');
75
- }
76
- const id = DocumentID.fromString(mutation.sub);
77
- const [doc, validator] = await Promise.all([
78
- ctx.db.getDocument(id),
79
- getDocumentValidator(ctx, id.model.toString())
80
- ]);
81
- const mergeDoc = mutation.data === null ? null : A.load(fromB64(mutation.data));
82
- const data = mergeDoc ? asType(validator, automergeToData(mergeDoc)) : null;
83
- if (doc === null) {
84
- return await ctx.db.createDocument({
85
- id,
86
- owner,
87
- data,
88
- state: mergeDoc ? A.save(mergeDoc) : null,
89
- unique: fromB64(mutation.unq)
90
- });
91
- }
92
- if (doc.owner !== owner) {
93
- throw new Error(`Cannot change owner from ${doc.owner} to ${owner} in document: ${id.toString()}`);
94
- }
95
- return await ctx.db.saveDocument({
96
- id,
97
- existing: doc,
98
- data,
99
- state: mergeDoc ? A.save(mergeDoc) : null
100
- });
101
- }
102
- default:
103
- throw new Error('Unsupported mutation type');
104
- }
9
+ return await apply(ctx, mutation);
105
10
  }
@@ -1 +1 @@
1
- {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../src/handlers/document.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAIvD,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,EAAE,OAAO,CAAA;CACZ,CAAA;AAED,wBAAgB,cAAc,CAC5B,cAAc,EAAE,oBAAoB,GACnC,iBAAiB,CAAC,gBAAgB,CAAC,CA+BrC"}
1
+ {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../src/handlers/document.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAEvD,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,EAAE,OAAO,CAAA;CACZ,CAAA;AAED,wBAAgB,cAAc,CAC5B,cAAc,EAAE,oBAAoB,GACnC,iBAAiB,CAAC,gBAAgB,CAAC,CA8BrC"}
@@ -1,11 +1,9 @@
1
- // import * as A from '@automerge/automerge/slim'
2
1
  import { toB64 } from '@enkaku/codec';
3
2
  export function createHandlers(handlersParams) {
4
3
  const { db } = handlersParams;
5
4
  return {
6
5
  'document/sync': async (ctx)=>{
7
6
  const ids = Object.keys(ctx.param.documents);
8
- // const [loadedStates] = await Promise.all([db.getDocumentStates(ids), automergeReady])
9
7
  const loadedStates = await db.getDocumentStates(ids);
10
8
  const states = ids.reduce((acc, id)=>{
11
9
  const loaded = loadedStates[id];
package/lib/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { type ExecutionContext, createContext } from './data/graphql.js';
1
2
  export { type CreateHandlersParams, createHandlers } from './handlers/index.js';
2
3
  export { type CreateClientParams, KubunServer, type ServerParams } from './server.js';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,oBAAoB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAC/E,OAAO,EAAE,KAAK,kBAAkB,EAAE,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACxE,OAAO,EAAE,KAAK,oBAAoB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAC/E,OAAO,EAAE,KAAK,kBAAkB,EAAE,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA"}
package/lib/index.js CHANGED
@@ -1,2 +1,3 @@
1
+ export { createContext } from './data/graphql.js';
1
2
  export { createHandlers } from './handlers/index.js';
2
3
  export { KubunServer } from './server.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubun/server",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "license": "see LICENSE.md",
5
5
  "keywords": [],
6
6
  "type": "module",
@@ -15,7 +15,6 @@
15
15
  ],
16
16
  "sideEffects": false,
17
17
  "dependencies": {
18
- "@automerge/automerge": "^2.2.8",
19
18
  "@enkaku/async": "^0.12.0",
20
19
  "@enkaku/codec": "^0.12.0",
21
20
  "@enkaku/generator": "^0.12.0",
@@ -24,18 +23,18 @@
24
23
  "@enkaku/token": "^0.12.0",
25
24
  "@enkaku/transport": "^0.12.0",
26
25
  "graphql": "^16.11.0",
27
- "@kubun/client": "^0.3.2",
28
- "@kubun/db": "^0.3.2",
26
+ "@kubun/client": "^0.3.3",
27
+ "@kubun/graphql": "^0.3.6",
28
+ "@kubun/db": "^0.3.3",
29
29
  "@kubun/id": "^0.3.0",
30
30
  "@kubun/protocol": "^0.3.4",
31
- "@kubun/graphql": "^0.3.5"
31
+ "@kubun/mutation": "^0.3.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@databases/pg-test": "^3.1.2",
35
35
  "@enkaku/stream": "^0.12.0",
36
- "graphql-relay": "^0.10.2",
37
- "@kubun/db-sqlite": "^0.3.2",
38
- "@kubun/db-postgres": "^0.3.2"
36
+ "@kubun/db-postgres": "^0.3.2",
37
+ "@kubun/db-sqlite": "^0.3.2"
39
38
  },
40
39
  "scripts": {
41
40
  "build:clean": "del lib",
@@ -1,5 +0,0 @@
1
- import * as A from '@automerge/automerge/slim';
2
- import type { DocumentData } from '@kubun/protocol';
3
- export declare const automergeReady: PromiseLike<void>;
4
- export declare function automergeToData(doc: A.Doc<DocumentData>): DocumentData;
5
- //# sourceMappingURL=automerge.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"automerge.d.ts","sourceRoot":"","sources":["../../src/data/automerge.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,CAAC,MAAM,2BAA2B,CAAA;AAE9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAEnD,eAAO,MAAM,cAAc,mBAA0D,CAAA;AAErF,wBAAgB,eAAe,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,YAAY,CAEtE"}
@@ -1,8 +0,0 @@
1
- // @ts-ignore missing type definition
2
- import { automergeWasmBase64 } from '@automerge/automerge/automerge.wasm.base64.js';
3
- import * as A from '@automerge/automerge/slim';
4
- import { lazy } from '@enkaku/async';
5
- export const automergeReady = lazy(()=>A.initializeBase64Wasm(automergeWasmBase64));
6
- export function automergeToData(doc) {
7
- return JSON.parse(JSON.stringify(doc));
8
- }