@kubun/server 0.4.3 → 0.5.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/lib/data/access-control.d.ts +1 -2
- package/lib/data/access-control.js +1 -164
- package/lib/data/graphql.d.ts +0 -1
- package/lib/data/graphql.js +1 -75
- package/lib/data/mutation-capture.d.ts +7 -0
- package/lib/data/mutation-capture.js +1 -0
- package/lib/data/mutation-log.d.ts +17 -0
- package/lib/data/mutation-log.js +1 -0
- package/lib/data/mutations.d.ts +3 -3
- package/lib/data/mutations.js +1 -114
- package/lib/handlers/document.d.ts +0 -1
- package/lib/handlers/document.js +1 -28
- package/lib/handlers/graph.d.ts +0 -1
- package/lib/handlers/graph.js +1 -191
- package/lib/handlers/index.d.ts +0 -1
- package/lib/handlers/index.js +1 -8
- package/lib/handlers/sync.d.ts +4 -0
- package/lib/handlers/sync.js +1 -0
- package/lib/handlers/types.d.ts +0 -1
- package/lib/handlers/types.js +1 -1
- package/lib/index.d.ts +0 -1
- package/lib/index.js +1 -3
- package/lib/server.d.ts +4 -2
- package/lib/server.js +1 -68
- package/lib/sync/crdt-merge.d.ts +26 -0
- package/lib/sync/crdt-merge.js +1 -0
- package/lib/sync/mutation-replay.d.ts +31 -0
- package/lib/sync/mutation-replay.js +1 -0
- package/lib/sync/peer-registry.d.ts +26 -0
- package/lib/sync/peer-registry.js +1 -0
- package/lib/sync/sync-client.d.ts +66 -0
- package/lib/sync/sync-client.js +1 -0
- package/lib/sync/sync-manager.d.ts +57 -0
- package/lib/sync/sync-manager.js +1 -0
- package/package.json +22 -21
- package/lib/data/access-control.d.ts.map +0 -1
- package/lib/data/graphql.d.ts.map +0 -1
- package/lib/data/mutations.d.ts.map +0 -1
- package/lib/handlers/document.d.ts.map +0 -1
- package/lib/handlers/graph.d.ts.map +0 -1
- package/lib/handlers/index.d.ts.map +0 -1
- package/lib/handlers/types.d.ts.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/server.d.ts.map +0 -1
|
@@ -18,7 +18,7 @@ export type AccessChecker = (doc: DocumentNode, permissionType: 'read' | 'write'
|
|
|
18
18
|
/**
|
|
19
19
|
* Parse and validate access permissions from document data
|
|
20
20
|
*/
|
|
21
|
-
export declare function parseDocumentAccessPermissions(data:
|
|
21
|
+
export declare function parseDocumentAccessPermissions(data: unknown): AccessPermissions | null;
|
|
22
22
|
/**
|
|
23
23
|
* Validate that DIDs have the correct format
|
|
24
24
|
*/
|
|
@@ -43,4 +43,3 @@ export declare function checkAccess(viewerDID: string | null, document: Document
|
|
|
43
43
|
* Create an access checker function bound to specific viewer and delegation tokens
|
|
44
44
|
*/
|
|
45
45
|
export declare function createAccessChecker(viewerDID: string | null, delegationTokens: Array<string> | undefined, db: KubunDB, serverConfig: ServerAccessConfig): AccessChecker;
|
|
46
|
-
//# sourceMappingURL=access-control.d.ts.map
|
|
@@ -1,164 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
/**
|
|
3
|
-
* Parse and validate access permissions from document data
|
|
4
|
-
*/ export function parseDocumentAccessPermissions(data) {
|
|
5
|
-
try {
|
|
6
|
-
if (!data.accessPermissions) return null;
|
|
7
|
-
const perms = data.accessPermissions;
|
|
8
|
-
// Basic validation - check if the permission object has the expected structure
|
|
9
|
-
if (typeof perms !== 'object') {
|
|
10
|
-
console.warn('Invalid accessPermissions structure in document, ignoring');
|
|
11
|
-
return null;
|
|
12
|
-
}
|
|
13
|
-
return perms;
|
|
14
|
-
} catch (error) {
|
|
15
|
-
console.warn('Failed to parse document access permissions:', error);
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Validate that DIDs have the correct format
|
|
21
|
-
*/ export function validateDIDs(dids) {
|
|
22
|
-
for (const did of dids){
|
|
23
|
-
if (!did.startsWith('did:')) {
|
|
24
|
-
throw new Error(`Invalid DID format: ${did}`);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Check if a level string is valid for the given permission type
|
|
30
|
-
*/ function isValidAccessLevel(level, permissionType) {
|
|
31
|
-
if (permissionType === 'read') {
|
|
32
|
-
return level === 'only_owner' || level === 'anyone' || level === 'allowed_dids';
|
|
33
|
-
}
|
|
34
|
-
// write permission
|
|
35
|
-
return level === 'only_owner' || level === 'allowed_dids';
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Resolve the effective access rule for a document and permission type
|
|
39
|
-
* Order of precedence:
|
|
40
|
-
* 1. Document accessPermissions override
|
|
41
|
-
* 2. User's model default from database
|
|
42
|
-
* 3. Server configuration default
|
|
43
|
-
*/ export async function resolveAccessRule(document, modelId, ownerDID, permissionType, db, serverConfig) {
|
|
44
|
-
// 1. Check document override
|
|
45
|
-
const docPerms = parseDocumentAccessPermissions(document.data);
|
|
46
|
-
if (docPerms?.[permissionType]) {
|
|
47
|
-
const rule = docPerms[permissionType];
|
|
48
|
-
if (rule && isValidAccessLevel(rule.level, permissionType)) {
|
|
49
|
-
return {
|
|
50
|
-
level: rule.level,
|
|
51
|
-
allowedDIDs: rule.allowedDIDs || null
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
// 2. Check user's model default
|
|
56
|
-
const modelDefault = await db.getUserModelAccessDefault(ownerDID, modelId, permissionType);
|
|
57
|
-
if (modelDefault) {
|
|
58
|
-
return {
|
|
59
|
-
level: modelDefault.level,
|
|
60
|
-
allowedDIDs: modelDefault.allowedDIDs
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
// 3. Fall back to server default
|
|
64
|
-
const level = serverConfig.defaultAccessLevel[permissionType];
|
|
65
|
-
return {
|
|
66
|
-
level,
|
|
67
|
-
allowedDIDs: null
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Check if viewer has access to document through delegation tokens
|
|
72
|
-
*/ export async function checkDelegation(viewerDID, grantor, document, permissionType, delegationTokens) {
|
|
73
|
-
if (!delegationTokens || delegationTokens.length === 0) {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
// Build expected permission based on specificity
|
|
77
|
-
const resources = [
|
|
78
|
-
`urn:kubun:document:${document.id}`,
|
|
79
|
-
`urn:kubun:model:${document.model}`,
|
|
80
|
-
'*'
|
|
81
|
-
];
|
|
82
|
-
const action = `document/${permissionType}` // document/read or document/write
|
|
83
|
-
;
|
|
84
|
-
// First, try tokens as a delegation chain (for A→B→C scenarios)
|
|
85
|
-
for (const res of resources){
|
|
86
|
-
try {
|
|
87
|
-
await checkCapability({
|
|
88
|
-
act: action,
|
|
89
|
-
res
|
|
90
|
-
}, {
|
|
91
|
-
iss: viewerDID,
|
|
92
|
-
sub: grantor,
|
|
93
|
-
cap: delegationTokens
|
|
94
|
-
});
|
|
95
|
-
return true;
|
|
96
|
-
} catch {}
|
|
97
|
-
}
|
|
98
|
-
// If chain validation fails, try each token independently (for multiple independent grants)
|
|
99
|
-
for (const token of delegationTokens){
|
|
100
|
-
for (const res of resources){
|
|
101
|
-
try {
|
|
102
|
-
await checkCapability({
|
|
103
|
-
act: action,
|
|
104
|
-
res
|
|
105
|
-
}, {
|
|
106
|
-
iss: viewerDID,
|
|
107
|
-
sub: grantor,
|
|
108
|
-
cap: token
|
|
109
|
-
});
|
|
110
|
-
return true;
|
|
111
|
-
} catch {}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return false;
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Check if viewer has access to a document for the specified permission type
|
|
118
|
-
*/ export async function checkAccess(viewerDID, document, permissionType, db, serverConfig, delegationTokens) {
|
|
119
|
-
// Validate document has owner
|
|
120
|
-
if (!document.owner) {
|
|
121
|
-
throw new Error('Document missing owner field');
|
|
122
|
-
}
|
|
123
|
-
// FAST PATH: Owner always has access
|
|
124
|
-
if (viewerDID === document.owner) {
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
|
-
// Resolve effective access rule
|
|
128
|
-
const rule = await resolveAccessRule(document, document.model, document.owner, permissionType, db, serverConfig);
|
|
129
|
-
// ANYONE: Always allow (read only)
|
|
130
|
-
if (rule.level === 'anyone') {
|
|
131
|
-
return true;
|
|
132
|
-
}
|
|
133
|
-
// No viewer: Deny
|
|
134
|
-
if (!viewerDID) {
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
// ONLY_OWNER: Check delegation from owner
|
|
138
|
-
if (rule.level === 'only_owner') {
|
|
139
|
-
return await checkDelegation(viewerDID, document.owner, document, permissionType, delegationTokens);
|
|
140
|
-
}
|
|
141
|
-
// ALLOWED_DIDS: Check if viewer is in list or has delegation
|
|
142
|
-
if (rule.level === 'allowed_dids') {
|
|
143
|
-
const allowedDIDs = rule.allowedDIDs || [];
|
|
144
|
-
if (allowedDIDs.includes(viewerDID)) {
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
147
|
-
// Check if any allowedDID has delegated to viewer
|
|
148
|
-
for (const allowedDID of allowedDIDs){
|
|
149
|
-
if (await checkDelegation(viewerDID, allowedDID, document, permissionType, delegationTokens)) {
|
|
150
|
-
return true;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return false;
|
|
154
|
-
}
|
|
155
|
-
// Unknown access level
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Create an access checker function bound to specific viewer and delegation tokens
|
|
160
|
-
*/ export function createAccessChecker(viewerDID, delegationTokens, db, serverConfig) {
|
|
161
|
-
return async (doc, permissionType)=>{
|
|
162
|
-
return await checkAccess(viewerDID, doc, permissionType, db, serverConfig, delegationTokens);
|
|
163
|
-
};
|
|
164
|
-
}
|
|
1
|
+
import{checkCapability as e}from"@enkaku/capability";export function parseDocumentAccessPermissions(e){try{if(!e||"object"!=typeof e||!("accessPermissions"in e))return null;let r=e.accessPermissions;if("object"!=typeof r)return console.warn("Invalid accessPermissions structure in document, ignoring"),null;return r}catch(e){return console.warn("Failed to parse document access permissions:",e),null}}export function validateDIDs(e){for(let r of e)if(!r.startsWith("did:"))throw Error(`Invalid DID format: ${r}`)}export async function resolveAccessRule(e,r,t,n,l,o){let s=parseDocumentAccessPermissions(e.data);if(s?.[n]){var c;let e=s[n];if(e&&(c=e.level,"read"===n?"only_owner"===c||"anyone"===c||"allowed_dids"===c:"only_owner"===c||"allowed_dids"===c))return{level:e.level,allowedDIDs:e.allowedDIDs||null}}let a=await l.getUserModelAccessDefault(t,r,n);return a?{level:a.level,allowedDIDs:a.allowedDIDs}:{level:o.defaultAccessLevel[n],allowedDIDs:null}}export async function checkDelegation(r,t,n,l,o){if(!o||0===o.length)return!1;let s=[`urn:kubun:document:${n.id}`,`urn:kubun:model:${n.model}`,"*"],c=`document/${l}`;for(let n of s)try{return await e({act:c,res:n},{iss:r,sub:t,cap:o}),!0}catch{}for(let n of o)for(let l of s)try{return await e({act:c,res:l},{iss:r,sub:t,cap:n}),!0}catch{}return!1}export async function checkAccess(e,r,t,n,l,o){if(!r.owner)throw Error("Document missing owner field");if(e===r.owner)return!0;let s=await resolveAccessRule(r,r.model,r.owner,t,n,l);if("anyone"===s.level)return!0;if(!e)return!1;if("only_owner"===s.level)return await checkDelegation(e,r.owner,r,t,o);if("allowed_dids"===s.level){let n=s.allowedDIDs||[];if(n.includes(e))return!0;for(let l of n)if(await checkDelegation(e,l,r,t,o))return!0}return!1}export function createAccessChecker(e,r,t,n){return async(l,o)=>await checkAccess(e,l,o,t,n,r)}
|
package/lib/data/graphql.d.ts
CHANGED
package/lib/data/graphql.js
CHANGED
|
@@ -1,75 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Kind, parse } from 'graphql';
|
|
3
|
-
import { removeDocumentAccessOverride, removeModelAccessDefaults, setDocumentAccessOverride, setModelAccessDefaults } from './mutations.js';
|
|
4
|
-
export function createContext(ctx) {
|
|
5
|
-
const readContext = createReadContext({
|
|
6
|
-
db: ctx.db,
|
|
7
|
-
viewerDID: ctx.viewerDID,
|
|
8
|
-
accessChecker: ctx.accessChecker
|
|
9
|
-
});
|
|
10
|
-
function getMutationDocument(info) {
|
|
11
|
-
return ctx.mutatedDocuments?.[info.path.key];
|
|
12
|
-
}
|
|
13
|
-
return {
|
|
14
|
-
...readContext,
|
|
15
|
-
async executeCreateMutation (_modelID, _data, info) {
|
|
16
|
-
return getMutationDocument(info);
|
|
17
|
-
},
|
|
18
|
-
async executeSetMutation (_modelID, _unique, _data, info) {
|
|
19
|
-
return getMutationDocument(info);
|
|
20
|
-
},
|
|
21
|
-
async executeUpdateMutation (_input, info) {
|
|
22
|
-
return getMutationDocument(info);
|
|
23
|
-
},
|
|
24
|
-
async executeRemoveMutation (_id, _info) {
|
|
25
|
-
// no-op
|
|
26
|
-
},
|
|
27
|
-
async executeSetModelAccessDefaults (modelId, permissionType, accessLevel, allowedDIDs) {
|
|
28
|
-
await setModelAccessDefaults({
|
|
29
|
-
ownerDID: ctx.viewerDID,
|
|
30
|
-
modelID: modelId,
|
|
31
|
-
permissionType,
|
|
32
|
-
accessLevel,
|
|
33
|
-
allowedDIDs
|
|
34
|
-
}, ctx.db);
|
|
35
|
-
},
|
|
36
|
-
async executeRemoveModelAccessDefaults (modelId, permissionTypes) {
|
|
37
|
-
await removeModelAccessDefaults({
|
|
38
|
-
ownerDID: ctx.viewerDID,
|
|
39
|
-
modelID: modelId,
|
|
40
|
-
permissionTypes
|
|
41
|
-
}, ctx.db);
|
|
42
|
-
},
|
|
43
|
-
async executeSetDocumentAccessOverride (documentId, permissionType, accessLevel, allowedDIDs) {
|
|
44
|
-
await setDocumentAccessOverride({
|
|
45
|
-
documentID: documentId,
|
|
46
|
-
permissionType,
|
|
47
|
-
accessLevel,
|
|
48
|
-
allowedDIDs
|
|
49
|
-
}, ctx.db);
|
|
50
|
-
},
|
|
51
|
-
async executeRemoveDocumentAccessOverride (documentId, permissionTypes) {
|
|
52
|
-
await removeDocumentAccessOverride({
|
|
53
|
-
documentID: documentId,
|
|
54
|
-
permissionTypes
|
|
55
|
-
}, ctx.db);
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
export function getExecutionArgs(params) {
|
|
60
|
-
const document = parse(params.text);
|
|
61
|
-
const definition = document.definitions[0];
|
|
62
|
-
if (definition == null) {
|
|
63
|
-
throw new Error('Missing GraphQL document definition');
|
|
64
|
-
}
|
|
65
|
-
// Ensure the operation matching the expected type
|
|
66
|
-
if (definition.kind !== Kind.OPERATION_DEFINITION || definition.operation !== params.type) {
|
|
67
|
-
throw new Error(`Invalid GraphQL document definition: expected ${params.type} operation`);
|
|
68
|
-
}
|
|
69
|
-
return {
|
|
70
|
-
document,
|
|
71
|
-
schema: params.schema,
|
|
72
|
-
variableValues: params.variables,
|
|
73
|
-
contextValue: createContext(params.context)
|
|
74
|
-
};
|
|
75
|
-
}
|
|
1
|
+
import{createReadContext as e}from"@kubun/graphql";import{Kind as t,parse as n}from"graphql";import{removeDocumentAccessOverride as c,removeModelAccessDefaults as o,setDocumentAccessOverride as a,setModelAccessDefaults as r}from"./mutations.js";export function createContext(t){function n(e){return t.mutatedDocuments?.[e.path.key]}return{...e({db:t.db,viewerDID:t.viewerDID,accessChecker:t.accessChecker}),executeCreateMutation:async(e,t,c)=>n(c),executeSetMutation:async(e,t,c,o)=>n(o),executeUpdateMutation:async(e,t)=>n(t),async executeRemoveMutation(e,t){},executeSetModelAccessDefaults:async(e,n,c,o)=>await r({ownerDID:t.viewerDID,modelID:e,permissionType:n,accessLevel:c,allowedDIDs:o},t.db),async executeRemoveModelAccessDefaults(e,n){await o({ownerDID:t.viewerDID,modelID:e,permissionTypes:n},t.db)},executeSetDocumentAccessOverride:async(e,n,c,o)=>await a({documentID:e,permissionType:n,accessLevel:c,allowedDIDs:o},t.db),async executeRemoveDocumentAccessOverride(e,n){await c({documentID:e,permissionTypes:n},t.db)}}}export function getExecutionArgs(e){let c=n(e.text),o=c.definitions[0];if(null==o)throw Error("Missing GraphQL document definition");if(o.kind!==t.OPERATION_DEFINITION||o.operation!==e.type)throw Error(`Invalid GraphQL document definition: expected ${e.type} operation`);return{document:c,schema:e.schema,variableValues:e.variables,contextValue:createContext(e.context)}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{sql as e}from"kysely";import{storeMutationLog as t}from"./mutation-log.js";export async function captureMutation(u){let{db:n,documentID:r,mutationPayload:m,authorDID:o}=u,i=await n.getDB(),a=await i.selectFrom("kubun_document_mutation_log").select("sequence_number").where("document_id","=",r).orderBy(e`CAST(sequence_number AS INTEGER)`,"desc").limit(1).executeTakeFirst(),c=a?String(Number(a.sequence_number)+1):"1";await t(n,{documentID:r,sequenceNumber:c,mutationJWT:m,authorDID:o})}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { DocumentMutationLog, KubunDB } from '@kubun/db';
|
|
2
|
+
export type MutationLogEntry = {
|
|
3
|
+
documentID: string;
|
|
4
|
+
sequenceNumber: string;
|
|
5
|
+
mutationJWT: string;
|
|
6
|
+
authorDID: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Stores a mutation log entry and maintains ring buffer of last 10 mutations per document
|
|
10
|
+
*/
|
|
11
|
+
export declare function storeMutationLog(db: KubunDB, entry: MutationLogEntry): Promise<void>;
|
|
12
|
+
/** @deprecated Use DocumentMutationLog from @kubun/db instead */
|
|
13
|
+
export type MutationLogRecord = DocumentMutationLog;
|
|
14
|
+
/**
|
|
15
|
+
* Retrieves mutation log entries for a document starting from a given sequence number
|
|
16
|
+
*/
|
|
17
|
+
export declare function getMutationLog(db: KubunDB, documentID: string, fromSequence: number): Promise<Array<DocumentMutationLog>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export async function storeMutationLog(t,o){await t.storeMutationLog(o)}export async function getMutationLog(t,o,n){return t.getMutationLog(o,n)}
|
package/lib/data/mutations.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { KubunDB } from '@kubun/db';
|
|
2
|
+
import type { ModelAccessDefaults } from '@kubun/graphql';
|
|
2
3
|
import { type MutationContext } from '@kubun/mutation';
|
|
3
4
|
import { type DocumentNode } from '@kubun/protocol';
|
|
4
5
|
import { type ServerAccessConfig } from './access-control.js';
|
|
@@ -12,7 +13,7 @@ export declare function setModelAccessDefaults(params: {
|
|
|
12
13
|
permissionType: 'read' | 'write';
|
|
13
14
|
accessLevel: string;
|
|
14
15
|
allowedDIDs: Array<string> | null;
|
|
15
|
-
}, db: KubunDB): Promise<
|
|
16
|
+
}, db: KubunDB): Promise<ModelAccessDefaults>;
|
|
16
17
|
/**
|
|
17
18
|
* Remove model-level access defaults for a user
|
|
18
19
|
*/
|
|
@@ -29,7 +30,7 @@ export declare function setDocumentAccessOverride(params: {
|
|
|
29
30
|
permissionType: 'read' | 'write';
|
|
30
31
|
accessLevel: string;
|
|
31
32
|
allowedDIDs: Array<string> | null;
|
|
32
|
-
}, db: KubunDB): Promise<
|
|
33
|
+
}, db: KubunDB): Promise<DocumentNode>;
|
|
33
34
|
/**
|
|
34
35
|
* Remove document-level access override
|
|
35
36
|
*/
|
|
@@ -38,4 +39,3 @@ export declare function removeDocumentAccessOverride(params: {
|
|
|
38
39
|
permissionTypes: Array<'read' | 'write'>;
|
|
39
40
|
}, db: KubunDB): Promise<void>;
|
|
40
41
|
export type { ServerAccessConfig };
|
|
41
|
-
//# sourceMappingURL=mutations.d.ts.map
|
package/lib/data/mutations.js
CHANGED
|
@@ -1,114 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { verifyToken } from '@enkaku/token';
|
|
3
|
-
import { DocumentID } from '@kubun/id';
|
|
4
|
-
import { applyMutation as apply } from '@kubun/mutation';
|
|
5
|
-
import { documentMutation } from '@kubun/protocol';
|
|
6
|
-
import { validateDIDs } from './access-control.js';
|
|
7
|
-
const validateMutation = createValidator(documentMutation);
|
|
8
|
-
export async function applyMutation(ctx, token) {
|
|
9
|
-
const verified = await verifyToken(token);
|
|
10
|
-
const mutation = asType(validateMutation, verified.payload);
|
|
11
|
-
return await apply(ctx, mutation);
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Set model-level access defaults for a user
|
|
15
|
-
*/ export async function setModelAccessDefaults(params, db) {
|
|
16
|
-
const { ownerDID, modelID, permissionType, accessLevel, allowedDIDs } = params;
|
|
17
|
-
// Validate DIDs if provided
|
|
18
|
-
if (allowedDIDs && allowedDIDs.length > 0) {
|
|
19
|
-
validateDIDs(allowedDIDs);
|
|
20
|
-
}
|
|
21
|
-
// Validate access level for permission type
|
|
22
|
-
const validLevels = {
|
|
23
|
-
read: [
|
|
24
|
-
'only_owner',
|
|
25
|
-
'anyone',
|
|
26
|
-
'allowed_dids'
|
|
27
|
-
],
|
|
28
|
-
write: [
|
|
29
|
-
'only_owner',
|
|
30
|
-
'allowed_dids'
|
|
31
|
-
]
|
|
32
|
-
};
|
|
33
|
-
if (!validLevels[permissionType].includes(accessLevel)) {
|
|
34
|
-
throw new Error(`Invalid access level "${accessLevel}" for permission type "${permissionType}"`);
|
|
35
|
-
}
|
|
36
|
-
await db.setUserModelAccessDefault({
|
|
37
|
-
ownerDID,
|
|
38
|
-
modelID,
|
|
39
|
-
permissionType,
|
|
40
|
-
accessLevel,
|
|
41
|
-
allowedDIDs
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Remove model-level access defaults for a user
|
|
46
|
-
*/ export async function removeModelAccessDefaults(params, db) {
|
|
47
|
-
const { ownerDID, modelID, permissionTypes } = params;
|
|
48
|
-
await db.removeUserModelAccessDefaults(ownerDID, modelID, permissionTypes);
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Set document-level access override
|
|
52
|
-
*/ export async function setDocumentAccessOverride(params, db) {
|
|
53
|
-
const { documentID, permissionType, accessLevel, allowedDIDs } = params;
|
|
54
|
-
// Validate DIDs if provided
|
|
55
|
-
if (allowedDIDs && allowedDIDs.length > 0) {
|
|
56
|
-
validateDIDs(allowedDIDs);
|
|
57
|
-
}
|
|
58
|
-
// Get the document
|
|
59
|
-
const docID = DocumentID.fromString(documentID);
|
|
60
|
-
const doc = await db.getDocument(docID);
|
|
61
|
-
if (!doc) {
|
|
62
|
-
throw new Error(`Document not found: ${documentID}`);
|
|
63
|
-
}
|
|
64
|
-
// Preserve existing permissions
|
|
65
|
-
const existingPerms = doc.data?.accessPermissions || {};
|
|
66
|
-
// Update the specific permission type
|
|
67
|
-
const updatedPerms = {
|
|
68
|
-
...existingPerms,
|
|
69
|
-
[permissionType]: {
|
|
70
|
-
level: accessLevel,
|
|
71
|
-
allowedDIDs
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
// Update the document with new access permissions
|
|
75
|
-
await db.saveDocument({
|
|
76
|
-
id: docID,
|
|
77
|
-
data: {
|
|
78
|
-
...doc.data,
|
|
79
|
-
accessPermissions: updatedPerms
|
|
80
|
-
},
|
|
81
|
-
state: null,
|
|
82
|
-
existing: doc
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Remove document-level access override
|
|
87
|
-
*/ export async function removeDocumentAccessOverride(params, db) {
|
|
88
|
-
const { documentID, permissionTypes } = params;
|
|
89
|
-
// Get the document
|
|
90
|
-
const docID = DocumentID.fromString(documentID);
|
|
91
|
-
const doc = await db.getDocument(docID);
|
|
92
|
-
if (!doc) {
|
|
93
|
-
// Silently return if document doesn't exist
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
// Preserve existing permissions, removing specified types
|
|
97
|
-
const existingPerms = doc.data?.accessPermissions || {};
|
|
98
|
-
const updatedPerms = {
|
|
99
|
-
...existingPerms
|
|
100
|
-
};
|
|
101
|
-
for (const permType of permissionTypes){
|
|
102
|
-
delete updatedPerms[permType];
|
|
103
|
-
}
|
|
104
|
-
// Update the document with updated access permissions
|
|
105
|
-
await db.saveDocument({
|
|
106
|
-
id: docID,
|
|
107
|
-
data: {
|
|
108
|
-
...doc.data,
|
|
109
|
-
accessPermissions: Object.keys(updatedPerms).length > 0 ? updatedPerms : undefined
|
|
110
|
-
},
|
|
111
|
-
state: null,
|
|
112
|
-
existing: doc
|
|
113
|
-
});
|
|
114
|
-
}
|
|
1
|
+
import{asType as e,createValidator as t}from"@enkaku/schema";import{verifyToken as o}from"@enkaku/token";import{DocumentID as a}from"@kubun/id";import{applyMutation as s}from"@kubun/mutation";import{documentMutation as r}from"@kubun/protocol";import{validateDIDs as l}from"./access-control.js";import{captureMutation as n}from"./mutation-capture.js";let i=t(r);export async function applyMutation(t,a){let r=e(i,(await o(a)).payload),l=await s(t,r);return await n({db:t.db,documentID:l.id,mutationPayload:a,authorDID:r.iss}),l}export async function setModelAccessDefaults(e,t){let{ownerDID:o,modelID:a,permissionType:s,accessLevel:r,allowedDIDs:n}=e;if(n&&n.length>0&&l(n),!({read:["only_owner","anyone","allowed_dids"],write:["only_owner","allowed_dids"]})[s].includes(r))throw Error(`Invalid access level "${r}" for permission type "${s}"`);await t.setUserModelAccessDefault({ownerDID:o,modelID:a,permissionType:s,accessLevel:r,allowedDIDs:n});let i=await t.getUserModelAccessDefault(o,a,"read"),c=await t.getUserModelAccessDefault(o,a,"write"),d={};return i&&(d.read={level:i.level,allowedDIDs:i.allowedDIDs??[]}),c&&(d.write={level:c.level,allowedDIDs:c.allowedDIDs??[]}),{ownerDID:o,modelId:a,permissions:d}}export async function removeModelAccessDefaults(e,t){let{ownerDID:o,modelID:a,permissionTypes:s}=e;await t.removeUserModelAccessDefaults(o,a,s)}export async function setDocumentAccessOverride(e,t){let{documentID:o,permissionType:s,accessLevel:r,allowedDIDs:n}=e;n&&n.length>0&&l(n);let i=a.fromString(o),c=await t.getDocument(i);if(!c)throw Error(`Document not found: ${o}`);let d={...c.data?.accessPermissions||{},[s]:{level:r,allowedDIDs:n}};return await t.saveDocument({id:i,data:{...c.data,accessPermissions:d},state:null,existing:c})}export async function removeDocumentAccessOverride(e,t){let{documentID:o,permissionTypes:s}=e,r=a.fromString(o),l=await t.getDocument(r);if(!l)return;let n={...l.data?.accessPermissions||{}};for(let e of s)delete n[e];await t.saveDocument({id:r,data:{...l.data,accessPermissions:Object.keys(n).length>0?n:void 0},state:null,existing:l})}
|
|
@@ -2,4 +2,3 @@ import type { ProcedureHandlers } from '@enkaku/server';
|
|
|
2
2
|
import type { DocumentProtocol } from '@kubun/protocol';
|
|
3
3
|
import type { CreateHandlersParams } from './types.js';
|
|
4
4
|
export declare function createHandlers(handlersParams: CreateHandlersParams): ProcedureHandlers<DocumentProtocol>;
|
|
5
|
-
//# sourceMappingURL=document.d.ts.map
|
package/lib/handlers/document.js
CHANGED
|
@@ -1,28 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export function createHandlers(handlersParams) {
|
|
3
|
-
const { db } = handlersParams;
|
|
4
|
-
return {
|
|
5
|
-
'document/sync': async (ctx)=>{
|
|
6
|
-
const ids = Object.keys(ctx.param.documents);
|
|
7
|
-
const loadedStates = await db.getDocumentStates(ids);
|
|
8
|
-
const states = ids.reduce((acc, id)=>{
|
|
9
|
-
const loaded = loadedStates[id];
|
|
10
|
-
if (loaded == null) {
|
|
11
|
-
acc[id] = null;
|
|
12
|
-
return acc;
|
|
13
|
-
}
|
|
14
|
-
const provided = ctx.param.documents[id];
|
|
15
|
-
if (provided == null) {
|
|
16
|
-
acc[id] = toB64(loaded);
|
|
17
|
-
} else {
|
|
18
|
-
// TODO: send diff rather than full doc state
|
|
19
|
-
acc[id] = toB64(loaded);
|
|
20
|
-
}
|
|
21
|
-
return acc;
|
|
22
|
-
}, {});
|
|
23
|
-
return {
|
|
24
|
-
states
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
}
|
|
1
|
+
import{toB64 as e}from"@enkaku/codec";export function createHandlers(t){let{db:n}=t;return{"document/sync":async t=>{let r=Object.keys(t.param.documents),a=await n.getDocumentStates(r);return{states:r.reduce((n,r)=>{let c=a[r];return null==c?n[r]=null:(t.param.documents[r],n[r]=e(c)),n},{})}}}}
|
package/lib/handlers/graph.d.ts
CHANGED
|
@@ -2,4 +2,3 @@ import type { ProcedureHandlers } from '@enkaku/server';
|
|
|
2
2
|
import type { GraphProtocol } from '@kubun/protocol';
|
|
3
3
|
import type { CreateHandlersParams } from './types.js';
|
|
4
4
|
export declare function createHandlers(handlersParams: CreateHandlersParams): ProcedureHandlers<GraphProtocol>;
|
|
5
|
-
//# sourceMappingURL=graph.d.ts.map
|
package/lib/handlers/graph.js
CHANGED
|
@@ -1,191 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { consume } from '@enkaku/generator';
|
|
3
|
-
import { createSchema } from '@kubun/graphql';
|
|
4
|
-
import { AttachmentID } from '@kubun/id';
|
|
5
|
-
import { GraphModel } from '@kubun/protocol';
|
|
6
|
-
import { execute, OperationTypeNode, // printSchema,
|
|
7
|
-
subscribe } from 'graphql';
|
|
8
|
-
import { createAccessChecker } from '../data/access-control.js';
|
|
9
|
-
import { getExecutionArgs } from '../data/graphql.js';
|
|
10
|
-
import { applyMutation } from '../data/mutations.js';
|
|
11
|
-
function toGraphResult(result) {
|
|
12
|
-
return {
|
|
13
|
-
data: result.data,
|
|
14
|
-
errors: result.errors?.map((err)=>err.toJSON()),
|
|
15
|
-
extensions: result.extensions
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
export function createHandlers(handlersParams) {
|
|
19
|
-
const { db, logger, serverAccessConfig } = handlersParams;
|
|
20
|
-
const graphModels = {};
|
|
21
|
-
async function getGraphModels(id) {
|
|
22
|
-
if (graphModels[id] == null) {
|
|
23
|
-
graphModels[id] = db.getGraph(id).then((graph)=>{
|
|
24
|
-
if (graph == null) {
|
|
25
|
-
logger.warn('graph {id} not found', {
|
|
26
|
-
id
|
|
27
|
-
});
|
|
28
|
-
delete graphModels[id];
|
|
29
|
-
throw new Error(`Graph not found: ${id}`);
|
|
30
|
-
}
|
|
31
|
-
logger.debug('cached model for graph {id}', {
|
|
32
|
-
id
|
|
33
|
-
});
|
|
34
|
-
return {
|
|
35
|
-
record: graph.record,
|
|
36
|
-
aliases: graph.aliases
|
|
37
|
-
};
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
return await graphModels[id];
|
|
41
|
-
}
|
|
42
|
-
const schemas = {};
|
|
43
|
-
async function getGraphQLSchema(id) {
|
|
44
|
-
if (schemas[id] == null) {
|
|
45
|
-
schemas[id] = getGraphModels(id).then((model)=>{
|
|
46
|
-
const schema = createSchema(model);
|
|
47
|
-
logger.debug('cached schema for graph {id}', {
|
|
48
|
-
id
|
|
49
|
-
});
|
|
50
|
-
// console.log(printSchema(schema))
|
|
51
|
-
return schema;
|
|
52
|
-
}).catch((err)=>{
|
|
53
|
-
delete schemas[id];
|
|
54
|
-
throw err;
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
return await schemas[id];
|
|
58
|
-
}
|
|
59
|
-
async function executeGraphQL(params) {
|
|
60
|
-
const result = await execute(getExecutionArgs(params));
|
|
61
|
-
return toGraphResult(result);
|
|
62
|
-
}
|
|
63
|
-
return {
|
|
64
|
-
'graph/deploy': async (ctx)=>{
|
|
65
|
-
const model = GraphModel.fromClusters({
|
|
66
|
-
clusters: ctx.param.clusters
|
|
67
|
-
});
|
|
68
|
-
const id = await db.createGraph({
|
|
69
|
-
id: ctx.param.id,
|
|
70
|
-
name: ctx.param.name,
|
|
71
|
-
record: model.record
|
|
72
|
-
});
|
|
73
|
-
logger.info('deployed graph {id}', {
|
|
74
|
-
id
|
|
75
|
-
});
|
|
76
|
-
return {
|
|
77
|
-
id,
|
|
78
|
-
...model.toJSON()
|
|
79
|
-
};
|
|
80
|
-
},
|
|
81
|
-
'graph/list': async ()=>{
|
|
82
|
-
const graphs = await db.listGraphs();
|
|
83
|
-
return {
|
|
84
|
-
graphs: graphs.map((graph)=>({
|
|
85
|
-
id: graph.id,
|
|
86
|
-
name: graph.name
|
|
87
|
-
}))
|
|
88
|
-
};
|
|
89
|
-
},
|
|
90
|
-
'graph/load': async (ctx)=>{
|
|
91
|
-
return await getGraphModels(ctx.param.id);
|
|
92
|
-
},
|
|
93
|
-
'graph/mutate': async (ctx)=>{
|
|
94
|
-
const attachments = Object.entries(ctx.param.attachments ?? {}).map(([key, value])=>{
|
|
95
|
-
const aid = AttachmentID.fromString(key);
|
|
96
|
-
return {
|
|
97
|
-
id: toB64(aid.digest),
|
|
98
|
-
data: fromB64(value)
|
|
99
|
-
};
|
|
100
|
-
});
|
|
101
|
-
if (attachments.length !== 0) {
|
|
102
|
-
await db.addAttachments(attachments);
|
|
103
|
-
}
|
|
104
|
-
// Apply mutations and make the documents available to the GraphQL resolvers in the context
|
|
105
|
-
const mutatedDocuments = {};
|
|
106
|
-
const validators = {};
|
|
107
|
-
await Promise.all(Object.entries(ctx.param.mutations).map(async ([key, mutation])=>{
|
|
108
|
-
mutatedDocuments[key] = await applyMutation({
|
|
109
|
-
db,
|
|
110
|
-
validators
|
|
111
|
-
}, mutation);
|
|
112
|
-
}));
|
|
113
|
-
const payload = ctx.message.payload;
|
|
114
|
-
const viewerDID = payload.sub || payload.iss;
|
|
115
|
-
const delegationTokens = payload.cap ? Array.isArray(payload.cap) ? payload.cap : [
|
|
116
|
-
payload.cap
|
|
117
|
-
] : undefined;
|
|
118
|
-
const accessChecker = createAccessChecker(viewerDID, delegationTokens, db, serverAccessConfig);
|
|
119
|
-
return await executeGraphQL({
|
|
120
|
-
schema: await getGraphQLSchema(ctx.param.id),
|
|
121
|
-
type: OperationTypeNode.MUTATION,
|
|
122
|
-
text: ctx.param.text,
|
|
123
|
-
variables: ctx.param.variables ?? {},
|
|
124
|
-
context: {
|
|
125
|
-
db,
|
|
126
|
-
mutatedDocuments,
|
|
127
|
-
viewerDID,
|
|
128
|
-
accessChecker
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
},
|
|
132
|
-
'graph/query': async (ctx)=>{
|
|
133
|
-
const payload = ctx.message.payload;
|
|
134
|
-
const viewerDID = payload.sub || payload.iss;
|
|
135
|
-
const delegationTokens = payload.cap ? Array.isArray(payload.cap) ? payload.cap : [
|
|
136
|
-
payload.cap
|
|
137
|
-
] : undefined;
|
|
138
|
-
const accessChecker = createAccessChecker(viewerDID, delegationTokens, db, serverAccessConfig);
|
|
139
|
-
return await executeGraphQL({
|
|
140
|
-
schema: await getGraphQLSchema(ctx.param.id),
|
|
141
|
-
type: OperationTypeNode.QUERY,
|
|
142
|
-
text: ctx.param.text,
|
|
143
|
-
variables: ctx.param.variables ?? {},
|
|
144
|
-
context: {
|
|
145
|
-
db,
|
|
146
|
-
viewerDID,
|
|
147
|
-
accessChecker
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
},
|
|
151
|
-
'graph/subscribe': async (ctx)=>{
|
|
152
|
-
const payload = ctx.message.payload;
|
|
153
|
-
const viewerDID = payload.sub || payload.iss;
|
|
154
|
-
const delegationTokens = payload.cap ? Array.isArray(payload.cap) ? payload.cap : [
|
|
155
|
-
payload.cap
|
|
156
|
-
] : undefined;
|
|
157
|
-
const accessChecker = createAccessChecker(viewerDID, delegationTokens, db, serverAccessConfig);
|
|
158
|
-
const args = getExecutionArgs({
|
|
159
|
-
schema: await getGraphQLSchema(ctx.param.id),
|
|
160
|
-
type: OperationTypeNode.SUBSCRIPTION,
|
|
161
|
-
text: ctx.param.text,
|
|
162
|
-
variables: ctx.param.variables ?? {},
|
|
163
|
-
context: {
|
|
164
|
-
db,
|
|
165
|
-
viewerDID,
|
|
166
|
-
accessChecker
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
const subscription = await subscribe(args);
|
|
170
|
-
if (ctx.signal.aborted) {
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
if ('errors' in subscription) {
|
|
174
|
-
return toGraphResult(subscription);
|
|
175
|
-
}
|
|
176
|
-
const writer = ctx.writable.getWriter();
|
|
177
|
-
try {
|
|
178
|
-
await consume(subscription, async (value)=>{
|
|
179
|
-
await writer.write(toGraphResult(value));
|
|
180
|
-
}, ctx.signal);
|
|
181
|
-
} catch (reason) {
|
|
182
|
-
if (reason !== 'Close') {
|
|
183
|
-
throw reason;
|
|
184
|
-
}
|
|
185
|
-
} finally{
|
|
186
|
-
await writer.close();
|
|
187
|
-
}
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
}
|
|
1
|
+
import{fromB64 as a,toB64 as e}from"@enkaku/codec";import{consume as r}from"@enkaku/generator";import{createSchema as t}from"@kubun/graphql";import{AttachmentID as i}from"@kubun/id";import{GraphModel as o}from"@kubun/protocol";import{execute as n,OperationTypeNode as s,subscribe as l}from"graphql";import{createAccessChecker as c}from"../data/access-control.js";import{getExecutionArgs as d}from"../data/graphql.js";import{applyMutation as m}from"../data/mutations.js";function p(a){return{data:a.data,errors:a.errors?.map(a=>a.toJSON()),extensions:a.extensions}}export function createHandlers(u){let{db:f,logger:h,serverAccessConfig:g}=u,y={};function b(a){return a?.fieldsMeta?Object.entries(a.fieldsMeta).filter(([a,e])=>!0===e.searchable).map(([a])=>a):[]}async function w(a,e,r){let t;h.info("starting backfill for model {modelID} in graph {graphID}",{modelID:e,graphID:a});let i=0,o=!0;for(;o;){let a=await f.queryDocuments({modelIDs:[e],first:100,after:t});for(let t of a.entries)t.document.data&&(await f.updateSearchEntry(e,t.document.id,t.document.data,r),i++);o=a.hasMore,t=a.entries.at(-1)?.cursor}h.info("backfill completed for model {modelID}: {total} documents indexed",{modelID:e,total:String(i)})}f.events.on("document:saved",async a=>{let e=a.document;for(let[a,r]of Object.entries(y)){let a=r[e.model];if(a&&a.length>0)try{null===e.data?await f.removeSearchEntry(e.model,e.id):await f.updateSearchEntry(e.model,e.id,e.data,a)}catch(a){h.error("failed to update search index for document {id}: {err}",{id:e.id,err:String(a)})}}});let x={};async function v(a){return null==x[a]&&(x[a]=f.getGraph(a).then(e=>{if(null==e)throw h.warn("graph {id} not found",{id:a}),delete x[a],Error(`Graph not found: ${a}`);if(h.debug("cached model for graph {id}",{id:a}),e.search){let r={};for(let[a,t]of Object.entries(e.search))r[a]=t.fields??b(e.record[a]);y[a]=r}return{record:e.record,aliases:e.aliases}})),await x[a]}let S={};async function O(a){return null==S[a]&&(S[a]=v(a).then(e=>{let r=t(e);return h.debug("cached schema for graph {id}",{id:a}),r}).catch(e=>{throw delete S[a],e})),await S[a]}async function j(a){return p(await n(d(a)))}return{"graph/deploy":async a=>{let e=o.fromClusters({clusters:a.param.clusters}),r=a.param.search,t=await f.createGraph({id:a.param.id,name:a.param.name,record:e.record,search:r});if(h.info("deployed graph {id}",{id:t}),r){for(let[a,i]of Object.entries(r)){let r=i.fields??b(e.record[a]);r.length>0&&(await f.createSearchIndex(a,r),h.info("created search index for model {modelID}",{modelID:a}),w(t,a,r).catch(e=>{h.error("backfill failed for model {modelID}: {err}",{modelID:a,err:String(e)})}))}let a={};for(let[t,i]of Object.entries(r))a[t]=i.fields??b(e.record[t]);y[t]=a}return delete x[t],delete S[t],{id:t,...e.toJSON(),search:r}},"graph/list":async()=>({graphs:(await f.listGraphs()).map(a=>({id:a.id,name:a.name}))}),"graph/load":async a=>await v(a.param.id),"graph/mutate":async r=>{let t=Object.entries(r.param.attachments??{}).map(([r,t])=>({id:e(i.fromString(r).digest),data:a(t)}));0!==t.length&&await f.addAttachments(t);let o={},n={};await Promise.all(Object.entries(r.param.mutations).map(async([a,e])=>{o[a]=await m({db:f,validators:n},e)}));let l=r.message.payload,d=l.sub||l.iss,p=c(d,l.cap?Array.isArray(l.cap)?l.cap:[l.cap]:void 0,f,g);return await j({schema:await O(r.param.id),type:s.MUTATION,text:r.param.text,variables:r.param.variables??{},context:{db:f,mutatedDocuments:o,viewerDID:d,accessChecker:p}})},"graph/query":async a=>{let e=a.message.payload,r=e.sub||e.iss,t=c(r,e.cap?Array.isArray(e.cap)?e.cap:[e.cap]:void 0,f,g);return await j({schema:await O(a.param.id),type:s.QUERY,text:a.param.text,variables:a.param.variables??{},context:{db:f,viewerDID:r,accessChecker:t}})},"graph/subscribe":async a=>{let e=a.message.payload,t=e.sub||e.iss,i=c(t,e.cap?Array.isArray(e.cap)?e.cap:[e.cap]:void 0,f,g),o=d({schema:await O(a.param.id),type:s.SUBSCRIPTION,text:a.param.text,variables:a.param.variables??{},context:{db:f,viewerDID:t,accessChecker:i}}),n=await l(o);if(a.signal.aborted)return null;if("errors"in n)return p(n);let m=a.writable.getWriter();try{await r(n,async a=>{await m.write(p(a))},a.signal)}catch(a){if("Close"!==a)throw a}finally{await m.close()}return null}}}
|
package/lib/handlers/index.d.ts
CHANGED
|
@@ -3,4 +3,3 @@ import type { Protocol } from '@kubun/protocol';
|
|
|
3
3
|
import type { CreateHandlersParams } from './types.js';
|
|
4
4
|
export type Handlers = ProcedureHandlers<Protocol>;
|
|
5
5
|
export declare function createHandlers(params: CreateHandlersParams): Handlers;
|
|
6
|
-
//# sourceMappingURL=index.d.ts.map
|
package/lib/handlers/index.js
CHANGED
|
@@ -1,8 +1 @@
|
|
|
1
|
-
import { createHandlers as
|
|
2
|
-
import { createHandlers as graphHandlers } from './graph.js';
|
|
3
|
-
export function createHandlers(params) {
|
|
4
|
-
return {
|
|
5
|
-
...documentHandlers(params),
|
|
6
|
-
...graphHandlers(params)
|
|
7
|
-
};
|
|
8
|
-
}
|
|
1
|
+
import{createHandlers as r}from"./document.js";import{createHandlers as e}from"./graph.js";import{createHandlers as a}from"./sync.js";export function createHandlers(s){return{...r(s),...e(s),...a(s)}}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ProcedureHandlers } from '@enkaku/server';
|
|
2
|
+
import type { SyncProtocol } from '@kubun/protocol';
|
|
3
|
+
import type { CreateHandlersParams } from './types.js';
|
|
4
|
+
export declare function createHandlers(handlersParams: CreateHandlersParams): ProcedureHandlers<SyncProtocol>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{checkCapability as e}from"@enkaku/capability";import{toB64 as t}from"@enkaku/codec";import{DocumentID as r}from"@kubun/id";import{blake3 as n}from"@noble/hashes/blake3.js";import{getMutationLog as o}from"../data/mutation-log.js";async function a(t,r,n){if(!n||0===n.length)return!1;let o=["*","urn:kubun:user:*",`urn:kubun:user:${r}`],a="document/read";for(let s of o)try{return await e({act:a,res:s},{iss:t,sub:r,cap:n}),!0}catch{}for(let s of n)for(let n of o)try{return await e({act:a,res:n},{iss:t,sub:r,cap:s}),!0}catch{}return!1}export function createHandlers(e){let{db:s,logger:c}=e;return{"sync/discovery":async e=>{let{userIDs:t,delegationTokens:r}=e.param,i=e.message.payload,u=i.sub||i.iss;c.debug("sync/discovery requested",{userIDs:t,viewerDID:u});let l=[];for(let e of t){let t=u===e,i=!t&&await a(u,e,r);if(!t&&!i){c.debug("sync/discovery: no access for user",{userID:e,viewerDID:u,hasDelegation:i});continue}for(let r of(c.debug("sync/discovery: access granted",{userID:e,viewerDID:u,isOwner:t,hasDelegation:i}),await s.listDocumentModelIDs()))try{for(let t of(await s.queryDocumentsByOwner(r,e))){let e=t.id,a=t.model??r,c=await s.getDocumentState(e),i=c?Array.from(n(c)).map(e=>e.toString(16).padStart(2,"0")).join("").slice(0,16):"",u=await o(s,e,0),m=u.length>0?u[u.length-1]:null,d=m?.timestamp??Date.now();l.push({documentID:e,modelID:a,lastMutationTimestamp:d,checkpointHash:i,sequenceNumber:m?.sequence_number??"0",priority:d})}}catch(e){c.debug("sync/discovery: skipping model table",{modelID:r,error:e})}}return l.sort((e,t)=>t.priority-e.priority),c.info("sync/discovery completed",{documentCount:l.length}),{documents:l}},"sync/stream":async e=>{let{documentIDs:n,delegationTokens:i}=e.param,u=e.message.payload,l=u.sub||u.iss;c.info("sync/stream started",{documentIDs:n,viewerDID:l});let m=0,d=0,y=e.writable.getWriter();try{for(let e of n){let n=await s.getDocumentState(e);if(!n){c.debug("sync/stream: document state not found",{documentID:e});continue}let u=r.fromString(e),f=await s.getDocument(u);if(!f){c.debug("sync/stream: document not found",{documentID:e});continue}let p=l===f.owner,w=!p&&await a(l,f.owner,i);if(!p&&!w){c.debug("sync/stream: access denied for document",{documentID:e,viewerDID:l,owner:f.owner}),await y.write({type:"skipped",documentID:e,reason:"access_denied"}),d++;continue}let g=await o(s,e,0);g.length>0&&g.length<=10?await y.write({type:"document",documentID:e,syncMode:"incremental",mutationJWTs:g.map(e=>e.mutation_jwt)}):await y.write({type:"document",documentID:e,syncMode:"full",fullState:t(n)}),m++}await y.write({type:"complete"})}catch(e){throw c.error("sync/stream error",{error:e}),e}finally{await y.close()}return c.info("sync/stream completed",{syncedDocuments:m,skippedDocuments:d}),{success:!0,syncedDocuments:m}},"sync/checkpoint":async e=>{let{peerServerID:t,checkpointData:r}=e.param;c.debug("sync/checkpoint storing",{peerServerID:t,userID:r.userID});let n=await s.storeSyncCheckpoint(t,r);return n?c.info("sync/checkpoint stored",{peerServerID:t,userID:r.userID}):c.error("sync/checkpoint failed to store"),{success:n}}}}
|
package/lib/handlers/types.d.ts
CHANGED
package/lib/handlers/types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export{};
|
package/lib/index.d.ts
CHANGED
|
@@ -2,4 +2,3 @@ export { createContext, type ExecutionContext } from './data/graphql.js';
|
|
|
2
2
|
export { createHandlers } from './handlers/index.js';
|
|
3
3
|
export type { CreateHandlersParams } from './handlers/types.js';
|
|
4
4
|
export { type CreateClientParams, KubunServer, type ServerParams } from './server.js';
|
|
5
|
-
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.js
CHANGED
|
@@ -1,3 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
export { createHandlers } from './handlers/index.js';
|
|
3
|
-
export { KubunServer } from './server.js';
|
|
1
|
+
export{createContext}from"./data/graphql.js";export{createHandlers}from"./handlers/index.js";export{KubunServer}from"./server.js";
|
package/lib/server.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { type Server } from '@enkaku/server';
|
|
2
|
+
import type { Identity } from '@enkaku/token';
|
|
2
3
|
import { type ClientParams, KubunClient } from '@kubun/client';
|
|
3
4
|
import { type DBParams, KubunDB } from '@kubun/db';
|
|
4
5
|
import { type Logger } from '@kubun/logger';
|
|
5
6
|
import type { Protocol, ServerTransport } from '@kubun/protocol';
|
|
7
|
+
import { SyncManager } from './sync/sync-manager.js';
|
|
6
8
|
export type ServerParams = {
|
|
7
9
|
access?: Record<string, boolean | Array<string>>;
|
|
8
10
|
db: KubunDB | DBParams;
|
|
9
|
-
|
|
11
|
+
identity: Identity;
|
|
10
12
|
logger?: Logger;
|
|
11
13
|
defaultAccessLevel?: {
|
|
12
14
|
read?: 'only_owner' | 'anyone' | 'allowed_dids';
|
|
@@ -24,7 +26,7 @@ export declare class KubunServer {
|
|
|
24
26
|
read: "only_owner" | "anyone" | "allowed_dids";
|
|
25
27
|
write: "only_owner" | "allowed_dids";
|
|
26
28
|
};
|
|
29
|
+
get sync(): SyncManager;
|
|
27
30
|
createClient(params: CreateClientParams): KubunClient;
|
|
28
31
|
serve(transport: ServerTransport, signal?: AbortSignal): Server<Protocol>;
|
|
29
32
|
}
|
|
30
|
-
//# sourceMappingURL=server.d.ts.map
|
package/lib/server.js
CHANGED
|
@@ -1,68 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { DirectTransports } from '@enkaku/transport';
|
|
3
|
-
import { KubunClient } from '@kubun/client';
|
|
4
|
-
import { KubunDB } from '@kubun/db';
|
|
5
|
-
import { getKubunLogger } from '@kubun/logger';
|
|
6
|
-
import { createHandlers } from './handlers/index.js';
|
|
7
|
-
export class KubunServer {
|
|
8
|
-
#access;
|
|
9
|
-
#db;
|
|
10
|
-
#handlers;
|
|
11
|
-
#id;
|
|
12
|
-
#logger;
|
|
13
|
-
#defaultAccessLevel;
|
|
14
|
-
constructor(params){
|
|
15
|
-
const { access, db, id } = params;
|
|
16
|
-
const logger = params.logger ?? getKubunLogger('server', {
|
|
17
|
-
serverID: id
|
|
18
|
-
});
|
|
19
|
-
this.#access = access ?? {};
|
|
20
|
-
this.#db = db instanceof KubunDB ? db : new KubunDB(db);
|
|
21
|
-
this.#id = id;
|
|
22
|
-
this.#logger = logger;
|
|
23
|
-
// Secure by default
|
|
24
|
-
this.#defaultAccessLevel = {
|
|
25
|
-
read: params.defaultAccessLevel?.read ?? 'only_owner',
|
|
26
|
-
write: params.defaultAccessLevel?.write ?? 'only_owner'
|
|
27
|
-
};
|
|
28
|
-
this.#handlers = createHandlers({
|
|
29
|
-
db: this.#db,
|
|
30
|
-
logger,
|
|
31
|
-
serverAccessConfig: {
|
|
32
|
-
defaultAccessLevel: this.#defaultAccessLevel
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
get db() {
|
|
37
|
-
return this.#db;
|
|
38
|
-
}
|
|
39
|
-
get defaultAccessLevel() {
|
|
40
|
-
return this.#defaultAccessLevel;
|
|
41
|
-
}
|
|
42
|
-
createClient(params) {
|
|
43
|
-
const { signal, ...clientParams } = params;
|
|
44
|
-
const transports = new DirectTransports({
|
|
45
|
-
signal
|
|
46
|
-
});
|
|
47
|
-
this.serve(transports.server, signal);
|
|
48
|
-
const logger = params.logger ?? this.#logger.getChild('client').with({
|
|
49
|
-
clientID: params.getRandomID?.() ?? crypto.randomUUID()
|
|
50
|
-
});
|
|
51
|
-
return new KubunClient({
|
|
52
|
-
logger,
|
|
53
|
-
serverID: this.#id,
|
|
54
|
-
transport: transports.client,
|
|
55
|
-
...clientParams
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
serve(transport, signal) {
|
|
59
|
-
return serve({
|
|
60
|
-
access: this.#access,
|
|
61
|
-
handlers: this.#handlers,
|
|
62
|
-
id: this.#id,
|
|
63
|
-
logger: this.#logger,
|
|
64
|
-
signal,
|
|
65
|
-
transport
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
}
|
|
1
|
+
import{serve as e}from"@enkaku/server";import{DirectTransports as t}from"@enkaku/transport";import{KubunClient as s}from"@kubun/client";import{KubunDB as r}from"@kubun/db";import{getKubunLogger as i}from"@kubun/logger";import{createHandlers as n}from"./handlers/index.js";import{SyncManager as l}from"./sync/sync-manager.js";export class KubunServer{#e;#t;#s;#r;#i;#n;#l;constructor(e){let{access:t,db:s,identity:c}=e,o=e.logger??i("server",{serverID:c.id});this.#e=t??{},this.#t=s instanceof r?s:new r(s),this.#r=c,this.#i=o,this.#n={read:e.defaultAccessLevel?.read??"only_owner",write:e.defaultAccessLevel?.write??"only_owner"},this.#s=n({db:this.#t,logger:o,serverAccessConfig:{defaultAccessLevel:this.#n}}),this.#l=new l({db:this.#t,identity:this.#r,logger:this.#i})}get db(){return this.#t}get defaultAccessLevel(){return this.#n}get sync(){return this.#l}createClient(e){let{signal:r,...i}=e,n=new t({signal:r});return this.serve(n.server,r),new s({logger:e.logger??this.#i.getChild("client").with({clientID:e.getRandomID?.()??crypto.randomUUID()}),serverID:this.#r.id,transport:n.client,...i})}serve(t,s){return e({access:this.#e,handlers:this.#s,identity:this.#r,logger:this.#i,signal:s,transport:t})}}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { KubunDB } from '@kubun/db';
|
|
2
|
+
import type { Logger } from '@kubun/logger';
|
|
3
|
+
export type CRDTMergeResult = {
|
|
4
|
+
documentID: string;
|
|
5
|
+
success: boolean;
|
|
6
|
+
merged: boolean;
|
|
7
|
+
error?: string;
|
|
8
|
+
};
|
|
9
|
+
export type MergeFullStateParams = {
|
|
10
|
+
db: KubunDB;
|
|
11
|
+
logger: Logger;
|
|
12
|
+
documentID: string;
|
|
13
|
+
fullState: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Merge an Automerge state received from a peer with the local document state.
|
|
17
|
+
*
|
|
18
|
+
* This handles the "full sync" mode where we receive a complete Automerge document
|
|
19
|
+
* and need to merge it with our local state using CRDT semantics.
|
|
20
|
+
*/
|
|
21
|
+
export declare function mergeFullState(params: MergeFullStateParams): Promise<CRDTMergeResult>;
|
|
22
|
+
/**
|
|
23
|
+
* Check if full sync merge is possible for a document.
|
|
24
|
+
* Full sync requires the document to already exist locally.
|
|
25
|
+
*/
|
|
26
|
+
export declare function canMergeFullState(db: KubunDB, documentID: string): Promise<boolean>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{automergeWasmBase64 as e}from"@automerge/automerge/automerge.wasm.base64";import*as t from"@automerge/automerge/slim";import{lazy as r}from"@enkaku/async";import{fromB64 as a}from"@enkaku/codec";import{asType as n,createValidator as o}from"@enkaku/schema";import{DocumentID as m}from"@kubun/id";let s=r(()=>t.initializeBase64Wasm(e)),l={};async function i(e,t){return null==l[t]&&(l[t]=e.getDocumentModel(t).then(e=>o({...e.schema,$id:t}))),l[t]}export async function mergeFullState(e){let{db:r,logger:o,documentID:l,fullState:u}=e;try{await s;let e=a(u),c=t.load(e),g=m.fromString(l),d=g.model.toString(),[f,p,y]=await Promise.all([r.getDocument(g),r.getDocumentState(l),i(r,d)]);if(null===f)return o.warn("Cannot apply full sync to non-existent document",{documentID:l}),{documentID:l,success:!1,merged:!1,error:"Document does not exist locally. Use incremental sync with create mutation."};let h=p?t.load(p):t.from(f.data||{}),S=t.merge(h,c),k=n(y,JSON.parse(JSON.stringify(S)));return await r.saveDocument({id:g,existing:f,data:k,state:t.save(S)}),o.info("Merged full state for document",{documentID:l,localHeads:t.getHeads(h).length,receivedHeads:t.getHeads(c).length,mergedHeads:t.getHeads(S).length}),{documentID:l,success:!0,merged:!0}}catch(t){let e=t instanceof Error?t.message:String(t);return o.error("Failed to merge full state",{documentID:l,error:e}),{documentID:l,success:!1,merged:!1,error:e}}}export async function canMergeFullState(e,t){try{let r=m.fromString(t),a=await e.getDocument(r);return null!==a}catch{return!1}}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { KubunDB } from '@kubun/db';
|
|
2
|
+
import type { Logger } from '@kubun/logger';
|
|
3
|
+
import type { DocumentNode } from '@kubun/protocol';
|
|
4
|
+
export type MutationReplayResult = {
|
|
5
|
+
documentID: string;
|
|
6
|
+
success: boolean;
|
|
7
|
+
error?: string;
|
|
8
|
+
};
|
|
9
|
+
export type ReplayMutationsParams = {
|
|
10
|
+
db: KubunDB;
|
|
11
|
+
logger: Logger;
|
|
12
|
+
mutationJWTs: string[];
|
|
13
|
+
/** Skip capturing mutations in local log (useful for avoiding duplicate captures) */
|
|
14
|
+
skipCapture?: boolean;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Replay mutation JWTs received from a peer server.
|
|
18
|
+
* This applies each mutation sequentially and captures them in the local mutation log
|
|
19
|
+
* for onward synchronization with other peers.
|
|
20
|
+
*/
|
|
21
|
+
export declare function replayMutations(params: ReplayMutationsParams): Promise<MutationReplayResult[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Replay a single mutation JWT.
|
|
24
|
+
* Returns the resulting document or throws on error.
|
|
25
|
+
*/
|
|
26
|
+
export declare function replaySingleMutation(params: {
|
|
27
|
+
db: KubunDB;
|
|
28
|
+
logger: Logger;
|
|
29
|
+
mutationJWT: string;
|
|
30
|
+
skipCapture?: boolean;
|
|
31
|
+
}): Promise<DocumentNode>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{asType as t,createValidator as a}from"@enkaku/schema";import{verifyToken as o}from"@enkaku/token";import{applyMutation as e}from"@kubun/mutation";import{documentMutation as r}from"@kubun/protocol";import{captureMutation as i}from"../data/mutation-capture.js";let u=a(r);export async function replayMutations(a){let{db:r,logger:n,mutationJWTs:s,skipCapture:c=!1}=a,d=[],m={db:r,validators:{},accessChecker:void 0};for(let a of s)try{let s=await o(a),p=t(u,s.payload),l=await e(m,p);c||await i({db:r,documentID:l.id,mutationPayload:a,authorDID:p.iss}),d.push({documentID:l.id,success:!0}),n.debug("Replayed mutation",{documentID:l.id,type:p.typ,author:p.iss})}catch(a){let t=a instanceof Error?a.message:String(a);n.error("Failed to replay mutation",{error:t}),d.push({documentID:"unknown",success:!1,error:t})}return d}export async function replaySingleMutation(a){let{db:r,logger:n,mutationJWT:s,skipCapture:c=!1}=a,d=t(u,(await o(s)).payload),m=await e({db:r,validators:{},accessChecker:void 0},d);return c||await i({db:r,documentID:m.id,mutationPayload:s,authorDID:d.iss}),n.debug("Replayed single mutation",{documentID:m.id,type:d.typ,author:d.iss}),m}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { KubunDB } from '@kubun/db';
|
|
2
|
+
export type PeerConfig = {
|
|
3
|
+
peerDID: string;
|
|
4
|
+
endpoint: string;
|
|
5
|
+
mode: 'persistent' | 'on-demand';
|
|
6
|
+
allowedUsers: Array<string> | {
|
|
7
|
+
all: boolean;
|
|
8
|
+
};
|
|
9
|
+
priority: string;
|
|
10
|
+
trustLevel: 'trusted' | 'restricted';
|
|
11
|
+
};
|
|
12
|
+
export type PeerConfigWithID = PeerConfig & {
|
|
13
|
+
id: string;
|
|
14
|
+
createdAt: number;
|
|
15
|
+
updatedAt: number;
|
|
16
|
+
};
|
|
17
|
+
export declare class PeerRegistry {
|
|
18
|
+
private db;
|
|
19
|
+
constructor(db: KubunDB);
|
|
20
|
+
addPeer(config: PeerConfig): Promise<void>;
|
|
21
|
+
getPeer(peerDID: string): Promise<PeerConfigWithID | undefined>;
|
|
22
|
+
listPeers(): Promise<Array<PeerConfigWithID>>;
|
|
23
|
+
updatePeer(peerDID: string, updates: Partial<PeerConfig>): Promise<void>;
|
|
24
|
+
removePeer(peerDID: string): Promise<void>;
|
|
25
|
+
isPeerAllowed(peerDID: string): Promise<boolean>;
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{nanoid as e}from"nanoid";export class PeerRegistry{db;constructor(e){this.db=e}async addPeer(t){let r=await this.db.getDB();await r.insertInto("kubun_sync_peers").values({id:e(),peer_did:t.peerDID,endpoint:t.endpoint,mode:t.mode,allowed_users:JSON.stringify(t.allowedUsers),priority:t.priority,trust_level:t.trustLevel,config:JSON.stringify({}),created_at:Date.now(),updated_at:Date.now()}).execute()}async getPeer(e){let t=await this.db.getDB(),r=await t.selectFrom("kubun_sync_peers").selectAll().where("peer_did","=",e).executeTakeFirst();if(r)return{id:r.id,peerDID:r.peer_did,endpoint:r.endpoint,mode:r.mode,allowedUsers:r.allowed_users,priority:r.priority,trustLevel:r.trust_level,createdAt:r.created_at,updatedAt:r.updated_at}}async listPeers(){let e=await this.db.getDB();return(await e.selectFrom("kubun_sync_peers").selectAll().execute()).map(e=>({id:e.id,peerDID:e.peer_did,endpoint:e.endpoint,mode:e.mode,allowedUsers:e.allowed_users,priority:e.priority,trustLevel:e.trust_level,createdAt:e.created_at,updatedAt:e.updated_at}))}async updatePeer(e,t){let r=await this.getPeer(e);if(!r)throw Error(`Peer ${e} not found`);let i={...r,...t},d=await this.db.getDB();await d.updateTable("kubun_sync_peers").set({endpoint:i.endpoint,mode:i.mode,allowed_users:JSON.stringify(i.allowedUsers),priority:i.priority,trust_level:i.trustLevel,config:JSON.stringify({}),updated_at:Date.now()}).where("peer_did","=",e).execute()}async removePeer(e){let t=await this.db.getDB();await t.deleteFrom("kubun_sync_peers").where("peer_did","=",e).execute()}async isPeerAllowed(e){return void 0!==await this.getPeer(e)}}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { SigningIdentity } from '@enkaku/token';
|
|
2
|
+
import { KubunClient } from '@kubun/client';
|
|
3
|
+
import type { Logger } from '@kubun/logger';
|
|
4
|
+
import type { KubunServer } from '../server.js';
|
|
5
|
+
export type SyncClientParams = {
|
|
6
|
+
identity: SigningIdentity;
|
|
7
|
+
logger: Logger;
|
|
8
|
+
serverResolver?: (serverID: string) => KubunServer | undefined;
|
|
9
|
+
};
|
|
10
|
+
export type SyncStreamMessage = {
|
|
11
|
+
type?: string;
|
|
12
|
+
documentID?: string;
|
|
13
|
+
syncMode?: string;
|
|
14
|
+
mutationJWTs?: Array<string>;
|
|
15
|
+
fullState?: string;
|
|
16
|
+
reason?: string;
|
|
17
|
+
};
|
|
18
|
+
export type DiscoveryDocument = {
|
|
19
|
+
documentID: string;
|
|
20
|
+
modelID: string;
|
|
21
|
+
lastMutationTimestamp: number;
|
|
22
|
+
checkpointHash: string;
|
|
23
|
+
sequenceNumber: string;
|
|
24
|
+
priority: number;
|
|
25
|
+
};
|
|
26
|
+
export type DiscoveryResult = {
|
|
27
|
+
documents: Array<DiscoveryDocument>;
|
|
28
|
+
};
|
|
29
|
+
export type SyncCheckpointData = {
|
|
30
|
+
userID: string;
|
|
31
|
+
lastSyncTimestamp: number;
|
|
32
|
+
documentCheckpoints: Record<string, {
|
|
33
|
+
lastKnownHash: string;
|
|
34
|
+
lastSyncedSequence: number;
|
|
35
|
+
}>;
|
|
36
|
+
vectorClock: Record<string, number>;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Client for connecting to peer servers for sync operations.
|
|
40
|
+
* Supports both direct (in-process) and HTTP transports.
|
|
41
|
+
*/
|
|
42
|
+
export declare class SyncClient {
|
|
43
|
+
#private;
|
|
44
|
+
constructor(params: SyncClientParams);
|
|
45
|
+
/**
|
|
46
|
+
* Connect to a peer server using the specified endpoint.
|
|
47
|
+
* - `direct://server-id` - Use in-process direct transport
|
|
48
|
+
* - `http://...` or `https://...` - Use HTTP transport (not yet implemented)
|
|
49
|
+
*/
|
|
50
|
+
connect(endpoint: string): Promise<KubunClient>;
|
|
51
|
+
/**
|
|
52
|
+
* Perform sync discovery to get document manifest from peer.
|
|
53
|
+
* Documents are returned sorted by priority (most recently modified first).
|
|
54
|
+
*/
|
|
55
|
+
discovery(client: KubunClient, userIDs: Array<string>, delegationTokens?: Array<string>): Promise<DiscoveryResult>;
|
|
56
|
+
/**
|
|
57
|
+
* Request documents via sync/stream and collect all messages.
|
|
58
|
+
*/
|
|
59
|
+
streamDocuments(client: KubunClient, documentIDs: Array<string>, delegationTokens?: Array<string>): Promise<Array<SyncStreamMessage>>;
|
|
60
|
+
/**
|
|
61
|
+
* Store a sync checkpoint on the peer server.
|
|
62
|
+
*/
|
|
63
|
+
storeCheckpoint(client: KubunClient, peerServerID: string, checkpointData: SyncCheckpointData): Promise<{
|
|
64
|
+
success: boolean;
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createArraySink as e}from"@enkaku/stream";import{DirectTransports as r}from"@enkaku/transport";import{KubunClient as t}from"@kubun/client";export class SyncClient{#e;#r;#t;constructor(e){this.#e=e.identity,this.#r=e.logger,this.#t=e.serverResolver}async connect(e){if(e.startsWith("direct://"))return this.#n(e);throw Error(`HTTP transport not yet implemented: ${e}`)}#n(e){let n=e.replace("direct://","");if(!this.#t)throw Error("Server resolver not configured for direct transport");let s=this.#t(n);if(!s)throw Error(`Server not found: ${n}`);let i=new r;return s.serve(i.server),new t({identity:this.#e,logger:this.#r.getChild("sync-client"),serverID:n,transport:i.client})}async discovery(e,r,t=[]){return e.client.request("sync/discovery",{param:{userIDs:r,delegationTokens:t}})}async streamDocuments(r,t,n=[]){let[s,i]=e(),o=r.client.createStream("sync/stream",{param:{documentIDs:t,delegationTokens:n}});return o.readable.pipeTo(s),await o,i}async storeCheckpoint(e,r,t){return e.client.request("sync/checkpoint",{param:{peerServerID:r,checkpointData:t}})}}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type Identity, type SigningIdentity } from '@enkaku/token';
|
|
2
|
+
import type { KubunDB } from '@kubun/db';
|
|
3
|
+
import type { Logger } from '@kubun/logger';
|
|
4
|
+
import type { KubunServer } from '../server.js';
|
|
5
|
+
import { type PeerConfig, type PeerConfigWithID } from './peer-registry.js';
|
|
6
|
+
export type SyncOptions = {
|
|
7
|
+
userIDs?: Array<string>;
|
|
8
|
+
documentIDs?: Array<string>;
|
|
9
|
+
};
|
|
10
|
+
export type SyncSessionInfo = {
|
|
11
|
+
peerID: string;
|
|
12
|
+
startTime: number;
|
|
13
|
+
documentsAttempted: number;
|
|
14
|
+
documentsCompleted: number;
|
|
15
|
+
};
|
|
16
|
+
export type SyncStatus = {
|
|
17
|
+
activeSessions: Array<SyncSessionInfo>;
|
|
18
|
+
lastSyncByPeer: Record<string, number>;
|
|
19
|
+
};
|
|
20
|
+
export type SyncEvent = {
|
|
21
|
+
type: 'started' | 'completed' | 'error' | 'document:complete' | 'document:failed' | 'document:skipped';
|
|
22
|
+
peerID: string;
|
|
23
|
+
documentID?: string;
|
|
24
|
+
timestamp: number;
|
|
25
|
+
error?: string;
|
|
26
|
+
reason?: string;
|
|
27
|
+
};
|
|
28
|
+
export type SyncEvents = {
|
|
29
|
+
sync: SyncEvent;
|
|
30
|
+
};
|
|
31
|
+
export type SyncManagerParams = {
|
|
32
|
+
db: KubunDB;
|
|
33
|
+
identity: Identity;
|
|
34
|
+
logger: Logger;
|
|
35
|
+
};
|
|
36
|
+
export declare class SyncManager {
|
|
37
|
+
#private;
|
|
38
|
+
constructor(params: SyncManagerParams);
|
|
39
|
+
/**
|
|
40
|
+
* Configure the signing identity used for sync operations.
|
|
41
|
+
*/
|
|
42
|
+
setIdentity(identity: SigningIdentity): void;
|
|
43
|
+
/**
|
|
44
|
+
* Configure the server resolver for direct transport connections.
|
|
45
|
+
*/
|
|
46
|
+
setServerResolver(resolver: (serverID: string) => KubunServer | undefined): void;
|
|
47
|
+
addPeer(config: PeerConfig): Promise<void>;
|
|
48
|
+
removePeer(peerDID: string): Promise<void>;
|
|
49
|
+
updatePeerConfig(peerDID: string, updates: Partial<PeerConfig>): Promise<void>;
|
|
50
|
+
listPeers(): Promise<Array<PeerConfigWithID>>;
|
|
51
|
+
getPeer(peerDID: string): Promise<PeerConfigWithID | undefined>;
|
|
52
|
+
syncWithPeer(peerDID: string, options?: SyncOptions): Promise<{
|
|
53
|
+
sessionID: string;
|
|
54
|
+
}>;
|
|
55
|
+
getStatus(peerDID?: string): SyncStatus;
|
|
56
|
+
onSyncEvent(callback: (event: SyncEvent) => void): () => void;
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{EventEmitter as e}from"@enkaku/event";import{isSigningIdentity as t}from"@enkaku/token";import{mergeFullState as r}from"./crdt-merge.js";import{replayMutations as n}from"./mutation-replay.js";import{PeerRegistry as s}from"./peer-registry.js";import{SyncClient as o}from"./sync-client.js";export class SyncManager{#e;#t;#r;#n=new e;#s=new Map;#o=new Map;#i;#m;constructor(e){this.#e=e.db,this.#i=e.identity,this.#t=e.logger,this.#r=new s(e.db)}setIdentity(e){this.#i=e}setServerResolver(e){this.#m=e}async addPeer(e){await this.#r.addPeer(e),this.#t.info("Peer added",{peerDID:e.peerDID})}async removePeer(e){await this.#r.removePeer(e),this.#t.info("Peer removed",{peerDID:e})}async updatePeerConfig(e,t){await this.#r.updatePeer(e,t),this.#t.info("Peer updated",{peerDID:e})}async listPeers(){return this.#r.listPeers()}async getPeer(e){return this.#r.getPeer(e)}async syncWithPeer(e,r){this.#t.info("Starting sync with peer",{peerDID:e,options:r});let n=await this.#r.getPeer(e);if(!n)throw Error(`Peer ${e} not found`);let s=`sync-${e}-${Date.now()}`,i={peerID:e,startTime:Date.now(),documentsAttempted:0,documentsCompleted:0};this.#s.set(s,i),this.#a({type:"started",peerID:e,timestamp:Date.now()});try{let m=this.#i;if(t(m)){let t=new o({identity:m,logger:this.#t,serverResolver:this.#m});this.#t.info("Connecting to peer",{endpoint:n.endpoint});let s=await t.connect(n.endpoint),a=r?.documentIDs??[];if(0===a.length&&r?.userIDs&&r.userIDs.length>0&&(this.#t.info("Discovering documents for users",{userIDs:r.userIDs}),a=(await t.discovery(s,r.userIDs)).documents.map(e=>e.documentID),this.#t.info("Discovered documents",{count:a.length})),0===a.length)this.#t.info("No documents to sync");else{for(let r of(i.documentsAttempted=a.length,this.#t.info("Requesting documents via stream",{count:a.length}),await t.streamDocuments(s,a)))if("document"===r.type&&r.documentID)try{await this.#c(r),i.documentsCompleted++,this.#a({type:"document:complete",peerID:e,documentID:r.documentID,timestamp:Date.now()})}catch(t){this.#t.error("Failed to apply document sync",{documentID:r.documentID,error:t}),this.#a({type:"document:failed",peerID:e,documentID:r.documentID,timestamp:Date.now(),error:t instanceof Error?t.message:String(t)})}else"skipped"===r.type&&r.documentID&&(this.#t.debug("Document skipped by peer",{documentID:r.documentID,reason:r.reason}),this.#a({type:"document:skipped",peerID:e,documentID:r.documentID,timestamp:Date.now(),reason:r.reason}));this.#t.info("Sync complete",{attempted:i.documentsAttempted,completed:i.documentsCompleted})}}else this.#t.warn("Sync session initiated without identity signer (skipping actual sync)",{sessionID:s,peerDID:e,endpoint:n.endpoint,options:r});this.#o.set(e,Date.now()),this.#a({type:"completed",peerID:e,timestamp:Date.now()})}catch(t){throw this.#t.error("Sync failed",{peerDID:e,error:t}),this.#a({type:"error",peerID:e,timestamp:Date.now(),error:t instanceof Error?t.message:String(t)}),t}finally{this.#s.delete(s)}return{sessionID:s}}async #c(e){if(!e.documentID)throw Error("Document ID is required");if("incremental"===e.syncMode&&e.mutationJWTs){this.#t.debug("Applying incremental sync",{documentID:e.documentID,mutationCount:e.mutationJWTs.length});let t=await n({db:this.#e,logger:this.#t,mutationJWTs:e.mutationJWTs,skipCapture:!1}),r=t.filter(e=>!e.success);r.length>0&&this.#t.warn("Some mutations failed to replay",{documentID:e.documentID,total:t.length,failed:r.length,errors:r.map(e=>e.error)}),this.#t.info("Replayed incremental mutations",{documentID:e.documentID,total:t.length,succeeded:t.length-r.length})}else if("full"===e.syncMode&&e.fullState){this.#t.debug("Applying full sync",{documentID:e.documentID});let t=await r({db:this.#e,logger:this.#t,documentID:e.documentID,fullState:e.fullState});if(!t.success)throw Error(t.error||"Failed to merge full state");this.#t.info("Applied full state sync",{documentID:e.documentID,merged:t.merged})}else throw Error(`Unknown sync mode: ${e.syncMode}`)}getStatus(e){let t=Array.from(this.#s.values()).filter(t=>!e||t.peerID===e).map(e=>({peerID:e.peerID,startTime:e.startTime,documentsAttempted:e.documentsAttempted,documentsCompleted:e.documentsCompleted})),r={};for(let[t,n]of this.#o.entries())e&&t!==e||(r[t]=n);return{activeSessions:t,lastSyncByPeer:r}}onSyncEvent(e){return this.#n.on("sync",e)}#a(e){this.#n.emit("sync",e).catch(e=>{this.#t.error("Error in sync event listener",{error:e})})}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"license": "see LICENSE.md",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"type": "module",
|
|
@@ -15,30 +15,31 @@
|
|
|
15
15
|
],
|
|
16
16
|
"sideEffects": false,
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@enkaku/async": "^0.
|
|
19
|
-
"@enkaku/capability": "^0.
|
|
20
|
-
"@enkaku/codec": "^0.
|
|
21
|
-
"@enkaku/
|
|
22
|
-
"@enkaku/
|
|
23
|
-
"@enkaku/
|
|
24
|
-
"@enkaku/
|
|
25
|
-
"@enkaku/
|
|
18
|
+
"@enkaku/async": "^0.13.0",
|
|
19
|
+
"@enkaku/capability": "^0.13.0",
|
|
20
|
+
"@enkaku/codec": "^0.13.0",
|
|
21
|
+
"@enkaku/event": "^0.13.0",
|
|
22
|
+
"@enkaku/generator": "^0.13.0",
|
|
23
|
+
"@enkaku/schema": "^0.13.0",
|
|
24
|
+
"@enkaku/server": "^0.13.0",
|
|
25
|
+
"@enkaku/token": "0.13.0",
|
|
26
|
+
"@enkaku/transport": "0.13.1",
|
|
26
27
|
"graphql": "^16.12.0",
|
|
27
|
-
"@kubun/
|
|
28
|
-
"@kubun/
|
|
29
|
-
"@kubun/
|
|
30
|
-
"@kubun/
|
|
31
|
-
"@kubun/protocol": "^0.
|
|
32
|
-
"@kubun/
|
|
33
|
-
"@kubun/id": "^0.
|
|
28
|
+
"@kubun/db": "^0.5.0",
|
|
29
|
+
"@kubun/graphql": "^0.5.0",
|
|
30
|
+
"@kubun/mutation": "^0.5.0",
|
|
31
|
+
"@kubun/client": "^0.5.0",
|
|
32
|
+
"@kubun/protocol": "^0.5.0",
|
|
33
|
+
"@kubun/logger": "^0.5.0",
|
|
34
|
+
"@kubun/id": "^0.5.0"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@databases/pg-test": "^3.1.2",
|
|
37
|
-
"@enkaku/stream": "^0.
|
|
38
|
-
"@kubun/db-
|
|
39
|
-
"@kubun/db-
|
|
40
|
-
"@kubun/
|
|
41
|
-
"@kubun/
|
|
38
|
+
"@enkaku/stream": "^0.13.0",
|
|
39
|
+
"@kubun/db-sqlite": "^0.5.0",
|
|
40
|
+
"@kubun/db-postgres": "^0.5.0",
|
|
41
|
+
"@kubun/test-utils": "^0.5.0",
|
|
42
|
+
"@kubun/scalars": "^0.5.0"
|
|
42
43
|
},
|
|
43
44
|
"scripts": {
|
|
44
45
|
"build:clean": "del lib",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"access-control.d.ts","sourceRoot":"","sources":["../../src/data/access-control.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAEnD,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAA;CAClC,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,KAAK,CAAC,EAAE,UAAU,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,kBAAkB,EAAE;QAClB,IAAI,EAAE,YAAY,GAAG,QAAQ,GAAG,cAAc,CAAA;QAC9C,KAAK,EAAE,YAAY,GAAG,cAAc,CAAA;KACrC,CAAA;CACF,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,CAC1B,GAAG,EAAE,YAAY,EACjB,cAAc,EAAE,MAAM,GAAG,OAAO,KAC7B,OAAO,CAAC,OAAO,CAAC,CAAA;AAErB;;GAEG;AACH,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,GAAG,GAAG,iBAAiB,GAAG,IAAI,CAiBlF;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAMtD;AAaD;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,GAAG,OAAO,EAChC,EAAE,EAAE,OAAO,EACX,YAAY,EAAE,kBAAkB,GAC/B,OAAO,CAAC,UAAU,CAAC,CA4BrB;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,YAAY,EACtB,cAAc,EAAE,MAAM,GAAG,OAAO,EAChC,gBAAgB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,GAC/B,OAAO,CAAC,OAAO,CAAC,CAoClB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,QAAQ,EAAE,YAAY,EACtB,cAAc,EAAE,MAAM,GAAG,OAAO,EAChC,EAAE,EAAE,OAAO,EACX,YAAY,EAAE,kBAAkB,EAChC,gBAAgB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,GAC/B,OAAO,CAAC,OAAO,CAAC,CA+DlB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,gBAAgB,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,EAC3C,EAAE,EAAE,OAAO,EACX,YAAY,EAAE,kBAAkB,GAC/B,aAAa,CAIf"}
|
|
@@ -1 +0,0 @@
|
|
|
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,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAQxD,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;IAC/C,aAAa,CAAC,EAAE,aAAa,CAAA;CAC9B,CAAA;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAgG5D;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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mutations.d.ts","sourceRoot":"","sources":["../../src/data/mutations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAExC,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAC9E,OAAO,EAAyB,KAAK,YAAY,EAAoB,MAAM,iBAAiB,CAAA;AAE5F,OAAO,EAAE,KAAK,kBAAkB,EAAgB,MAAM,qBAAqB,CAAA;AAI3E,wBAAsB,aAAa,CAAC,GAAG,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAI9F;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE;IACN,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,cAAc,EAAE,MAAM,GAAG,OAAO,CAAA;IAChC,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAA;CAClC,EACD,EAAE,EAAE,OAAO,GACV,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED;;GAEG;AACH,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE;IACN,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,eAAe,EAAE,KAAK,CAAC,MAAM,GAAG,OAAO,CAAC,CAAA;CACzC,EACD,EAAE,EAAE,OAAO,GACV,OAAO,CAAC,IAAI,CAAC,CAGf;AAED;;GAEG;AACH,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE;IACN,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,MAAM,GAAG,OAAO,CAAA;IAChC,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAA;CAClC,EACD,EAAE,EAAE,OAAO,GACV,OAAO,CAAC,IAAI,CAAC,CAqCf;AAED;;GAEG;AACH,wBAAsB,4BAA4B,CAChD,MAAM,EAAE;IACN,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,KAAK,CAAC,MAAM,GAAG,OAAO,CAAC,CAAA;CACzC,EACD,EAAE,EAAE,OAAO,GACV,OAAO,CAAC,IAAI,CAAC,CA6Bf;AAGD,YAAY,EAAE,kBAAkB,EAAE,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
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,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAEvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAEtD,wBAAgB,cAAc,CAC5B,cAAc,EAAE,oBAAoB,GACnC,iBAAiB,CAAC,gBAAgB,CAAC,CA8BrC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../src/handlers/graph.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAKvD,OAAO,KAAK,EAIV,aAAa,EACd,MAAM,iBAAiB,CAAA;AAexB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAUtD,wBAAgB,cAAc,CAC5B,cAAc,EAAE,oBAAoB,GACnC,iBAAiB,CAAC,aAAa,CAAC,CAqKlC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AACvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAI/C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAEtD,MAAM,MAAM,QAAQ,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAA;AAElD,wBAAgB,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,QAAQ,CAKrE"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/handlers/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAE3C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAEnE,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,EAAE,OAAO,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,kBAAkB,EAAE,kBAAkB,CAAA;CACvC,CAAA"}
|
package/lib/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,YAAY,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC/D,OAAO,EAAE,KAAK,kBAAkB,EAAE,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA"}
|
package/lib/server.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAS,MAAM,gBAAgB,CAAA;AAEnD,OAAO,EAAE,KAAK,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC9D,OAAO,EAAE,KAAK,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAClD,OAAO,EAAkB,KAAK,MAAM,EAAE,MAAM,eAAe,CAAA;AAC3D,OAAO,KAAK,EAAiB,QAAQ,EAAiB,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAI9F,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAA;IAChD,EAAE,EAAE,OAAO,GAAG,QAAQ,CAAA;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kBAAkB,CAAC,EAAE;QACnB,IAAI,CAAC,EAAE,YAAY,GAAG,QAAQ,GAAG,cAAc,CAAA;QAC/C,KAAK,CAAC,EAAE,YAAY,GAAG,cAAc,CAAA;KACtC,CAAA;CACF,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,YAAY,EAAE,UAAU,GAAG,WAAW,CAAC,GAAG;IAC9E,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB,CAAA;AAED,qBAAa,WAAW;;gBAWV,MAAM,EAAE,YAAY;IAmBhC,IAAI,EAAE,IAAI,OAAO,CAEhB;IAED,IAAI,kBAAkB;cA3Bd,YAAY,GAAG,QAAQ,GAAG,cAAc;eACvC,YAAY,GAAG,cAAc;MA4BrC;IAED,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,WAAW;IAkBrD,KAAK,CAAC,SAAS,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC;CAU1E"}
|