@kubun/engine 0.8.0 → 0.8.2

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,15 @@
1
- import type { KubunDB } from '@kubun/db';
2
1
  import type { DocumentNode } from '@kubun/protocol';
2
+ /**
3
+ * Minimal database interface required by the access control system.
4
+ * Decoupled from KubunDB so any store implementation can provide these methods.
5
+ */
6
+ export type AccessControlDB = {
7
+ getUserModelAccessDefault(ownerDID: string, modelID: string, permissionType: 'read' | 'write'): Promise<{
8
+ level: string;
9
+ allowedDIDs: Array<string> | null;
10
+ } | null>;
11
+ isMemberOfAnyCircle(viewerDID: string, circleIDs: Array<string>): Promise<boolean>;
12
+ };
3
13
  export type AccessLevel = 'only_owner' | 'anyone' | 'allowed_dids';
4
14
  export type AccessRule = {
5
15
  level: AccessLevel;
@@ -30,6 +40,6 @@ export declare function validateDIDs(dids: Array<string>): void;
30
40
  export declare function createAccessChecker(params: {
31
41
  viewerDID: string | undefined;
32
42
  delegationTokens?: Array<string>;
33
- db: KubunDB;
43
+ db: AccessControlDB;
34
44
  defaultAccessLevel: DefaultAccessLevel;
35
45
  }): AccessChecker;
@@ -1,3 +1,4 @@
1
+ import type { DocumentSavedEvent } from '@kubun/graphql';
1
2
  /**
2
3
  * Event types emitted by the engine during document operations.
3
4
  *
@@ -5,6 +6,7 @@
5
6
  * observed by plugins or application code via the EngineEventBus.
6
7
  */
7
8
  export type EngineEvents = {
9
+ 'document:saved': DocumentSavedEvent;
8
10
  'engine:document:created': {
9
11
  documentID: string;
10
12
  modelID: string;
package/lib/engine.d.ts CHANGED
@@ -1,35 +1,16 @@
1
1
  import { type Runtime } from '@enkaku/runtime';
2
2
  import type { Identity } from '@enkaku/token';
3
- import { type DBParams, KubunDB } from '@kubun/db';
3
+ import { KubunDB, type StoreProvider } from '@kubun/db';
4
4
  import type { Adapter } from '@kubun/db-adapter';
5
5
  import { type Logger } from '@kubun/logger';
6
6
  import type { DeployGraphParams, DeployGraphResult, DocumentNode, ExecuteGraphResult, ListGraphResult, LoadGraphParams, LoadGraphResult } from '@kubun/protocol';
7
7
  import { type DocumentMutation } from '@kubun/protocol';
8
8
  import type { GraphQLSchema } from 'graphql';
9
- import type { Migration, Transaction } from 'kysely';
10
9
  import type { EngineEvents } from './engine-events.js';
11
10
  import { EngineEventBus } from './events.js';
12
11
  import type { Engine, EngineGraphParams, GraphQLSource, GraphQLSourceParams } from './executor.js';
13
12
  import type { KubunPlugin, PluginFactoryParams } from './plugin.js';
14
13
  export type { Engine };
15
- export type PluginMigrationContext = {
16
- types: Adapter['types'];
17
- functions: Adapter['functions'];
18
- };
19
- export type PluginMigrations = {
20
- name: string;
21
- migrations: Record<string, Migration> | ((context: PluginMigrationContext) => Record<string, Migration>);
22
- };
23
- /**
24
- * Transactional context passed to the callback within ExecutionContext.transaction().
25
- * Provides a Kysely transaction handle and buffered event emission.
26
- */
27
- export type TransactionContext = {
28
- /** Kysely transaction handle — same query builder API as Kysely<Database> */
29
- db: Transaction<Record<string, unknown>>;
30
- /** Emit an event that will be buffered until the transaction commits */
31
- emit(name: string, data: unknown): void;
32
- };
33
14
  /**
34
15
  * Per-request execution context provided to plugin operations.
35
16
  * Only carries what's per-request — shared resources (identity, hlc, logger, etc.)
@@ -37,16 +18,14 @@ export type TransactionContext = {
37
18
  */
38
19
  export type ExecutionContext = {
39
20
  viewerDID: string;
40
- /** On-demand transaction factory — not created until called */
41
- transaction<R>(fn: (tx: TransactionContext) => Promise<R>): Promise<R>;
42
21
  };
43
22
  /**
44
23
  * A function that produces per-request context fields.
45
24
  * Called by the engine for each execute()/subscribe() invocation.
46
25
  */
47
- export type ContextFactory = (executionContext: ExecutionContext) => Record<string, unknown>;
26
+ export type ContextFactory = (executionContext: ExecutionContext, stores: StoreProvider) => Record<string, unknown>;
48
27
  export type EngineParams = {
49
- db: DBParams | KubunDB;
28
+ db: Adapter | KubunDB;
50
29
  identity: Identity;
51
30
  logger?: Logger;
52
31
  runtime?: Partial<Runtime>;
@@ -83,6 +62,8 @@ export type ExecuteParams = {
83
62
  variables?: Record<string, unknown>;
84
63
  viewerDID?: string;
85
64
  contextExtensions?: Record<string, unknown>;
65
+ /** Override the store provider for this execution (e.g. transactional provider). */
66
+ stores?: StoreProvider;
86
67
  };
87
68
  export declare class KubunEngine implements Engine {
88
69
  #private;
@@ -90,11 +71,6 @@ export declare class KubunEngine implements Engine {
90
71
  get did(): string;
91
72
  get identity(): Identity;
92
73
  get eventBus(): EngineEventBus<EngineEvents>;
93
- /**
94
- * Resolves when the engine has finished initializing (migrations, plugins, schema hooks).
95
- * Consumers must await this before calling execute() or deploy().
96
- */
97
- get ready(): Promise<void>;
98
74
  /**
99
75
  * Dispose the engine by shutting down all plugins in parallel.
100
76
  */
package/lib/engine.js CHANGED
@@ -1 +1 @@
1
- import{createRuntime as e}from"@enkaku/runtime";import{asType as t,createValidator as i}from"@enkaku/schema";import{isSigningIdentity as s,stringifyToken as n,verifyToken as a}from"@enkaku/token";import{KubunDB as r}from"@kubun/db";import{createReadContext as o,createSchema as l}from"@kubun/graphql";import{HLC as u}from"@kubun/hlc";import{getKubunLogger as c}from"@kubun/logger";import{applyChangeMutation as d,applyMutation as h,applySetMutation as m,convertPatchInput as p,createMutationOperations as g}from"@kubun/mutation";import{clusterToRecord as y,documentMutation as w,GraphModel as v}from"@kubun/protocol";import{execute as f,GraphQLError as b,Kind as D,parse as x,subscribe as I}from"graphql";import{Migrator as M}from"kysely";import{EngineEventBus as E}from"./events.js";import{computeMutationHash as k}from"./mutation-hash.js";import{runPolicies as G}from"./policies.js";import{createRegistry as O}from"./registry.js";let P=i(w);export class KubunEngine{#e=[];#t;#i;#s={};#n;#a;#r;#o={};#l=new Map;#u=[];#c;#d;#h;#m={};#p={};constructor(t){this.#t=t.db instanceof r?t.db:new r(t.db),this.#a=t.identity,this.#n=new u({nodeID:t.identity.id}),this.#r=t.logger??c("engine"),this.#i=t.eventBus??new E({logger:this.#r.getChild("events")}),this.#h=e(t.runtime),this.#d=O(),this.#c=this.#g(t)}get did(){return this.#a.id}get identity(){return this.#a}get eventBus(){return this.#i}get ready(){return this.#c}async dispose(){await Promise.all(this.#u.filter(e=>null!=e.dispose).map(e=>e.dispose?.()))}getGraphQLSource(e){let{graphID:t}=e,i=e.getViewerDID??(()=>this.#a.id);return{query:async e=>await this.queryGraph({id:t,text:e.text,variables:e.variables,viewerDID:i()??void 0}),mutate:async e=>await this.mutateGraph({id:t,text:e.text,variables:e.variables,viewerDID:i()??void 0}),subscribe:e=>this.#y({graphID:t,text:e.text,variables:e.variables,viewerDID:i()??void 0})}}registerContextFactory(e){this.#e.push(e)}#w(e){let t=e.viewerDID??this.#a.id,i=this.#v(t),s={};for(let e of this.#e)s={...s,...e(i)};null!=e.contextExtensions&&(s={...s,...e.contextExtensions});let n=e.graphID;return this.#p[n]??={},function(e){let t={db:e.db,validators:e.validators,hlc:e.hlc},i=g({issuer:e.viewerDID,hlc:e.hlc,getRandomValues:e.runtime.getRandomValues,processSetMutation:e=>m(t,e),processChangeMutation:e=>d(t,e)}),{eventBus:s,viewerDID:n}=e,a={...o({db:e.db,viewerDID:e.viewerDID}),async executeCreateMutation({modelID:e,data:t}){let a=await i.createDocument({modelID:e,data:t});return await s.emit("engine:document:created",{documentID:a.id,modelID:a.model,viewerDID:n}),await s.emit("engine:mutation:applied",{type:"create",documentID:a.id,viewerDID:n}),a},executeSetMutation:async({modelID:e,unique:t,data:s})=>await i.setDocument({modelID:e,unique:t,data:s}),async executeUpdateMutation({input:e}){let t=await i.updateDocument({docID:e.id,patch:p(e.patch)});return await s.emit("engine:document:updated",{documentID:t.id,modelID:t.model,viewerDID:n}),await s.emit("engine:mutation:applied",{type:"update",documentID:t.id,viewerDID:n}),t},async executeRemoveMutation({id:e}){await i.removeDocument({docID:e}),await s.emit("engine:document:removed",{documentID:e,viewerDID:n}),await s.emit("engine:mutation:applied",{type:"remove",documentID:e,viewerDID:n})},async executeSetModelAccessDefaults(){throw Error("Access control mutations are not supported in engine mode")},async executeRemoveModelAccessDefaults(){throw Error("Access control mutations are not supported in engine mode")},async executeSetDocumentAccessOverride(){throw Error("Access control mutations are not supported in engine mode")},async executeRemoveDocumentAccessOverride(){throw Error("Access control mutations are not supported in engine mode")},beginTransaction(){throw Error("Transaction mutations are not supported in engine mode")},async commitTransaction(){throw Error("Transaction mutations are not supported in engine mode")},rollbackTransaction(){throw Error("Transaction mutations are not supported in engine mode")}};return null!=e.contextExtensions?{...a,...e.contextExtensions}:a}({db:this.#t,viewerDID:t,hlc:this.#n,runtime:this.#h,validators:this.#p[n],eventBus:this.#i,contextExtensions:Object.keys(s).length>0?s:void 0})}#v(e){let t=this.#t,i=this.#h,s=this.#i;return{viewerDID:e,async transaction(e){let n=i.getRandomID();s.beginTransaction(n);try{let i=await t.getDB(),a=await i.transaction().execute(async t=>await e({db:t,emit(e,t){s.emitBuffered(n,e,t)}}));return await s.commit(n),a}catch(e){throw s.rollback(n),e}}}}async #g(e){let t=e.plugins??[],i={engine:this,graph:{execute:e=>this.#f(e),subscribe:e=>this.#y(e),applyVerifiedMutation:e=>this.#b(e),applyVerifiedMutations:e=>this.#D(e)},db:this.#t,runtime:this.#h,identity:this.#a,eventBus:this.#i,hlc:this.#n,getLogger:e=>this.#r.getChild(e)};for(let e of t){let t=e(i);this.#u.push(t),null!=t.api&&this.#d.registerPlugin(t.name,t.api)}this.#d.closeGate();let s=[];for(let e of this.#u)null!=e.migrations&&s.push(e.migrations);for(let e of(s.length>0&&await this.#x(s),this.#u))null!=e.schemaExtension&&this.#l.set(e.name,e.schemaExtension);for(let e of this.#u)null!=e.createContextFactory&&this.#e.push(e.createContextFactory());for(let e of this.#u)if(null!=e.policies)for(let[t,i]of Object.entries(e.policies)){null==this.#o[t]&&(this.#o[t]={sync:[],async:[]});let e=this.#o[t];null!=i.sync&&(e.sync=[...e.sync??[],...i.sync]),null!=i.async&&(e.async=[...e.async??[],...i.async])}this.#r.info("engine initialized with {count} plugins",{count:this.#u.length})}async #x(e){if(0===e.length)return;let t=await this.#t.getDB(),i=this.#t.adapter,s={types:i.types,functions:i.functions};for(let i of e){let e="function"==typeof i.migrations?i.migrations(s):i.migrations,n=new M({db:t,provider:{getMigrations:()=>Promise.resolve(e)},migrationTableName:`kubun_${i.name}_migration`,migrationLockTableName:`kubun_${i.name}_migration_lock`}),a=await n.migrateToLatest();if(null!=a.error)throw a.error;this.#r.info("plugin {name} migrations complete",{name:i.name})}}async #I(e){return null==this.#s[e]&&(this.#s[e]=this.#t.getGraph(e).then(t=>{if(null==t)throw this.#r.warn("graph {id} not found",{id:e}),delete this.#s[e],Error(`Graph not found: ${e}`);return this.#r.debug("cached model for graph {id}",{id:e}),{aliases:t.aliases,record:t.record,extensionSDL:t.extension_sdl??void 0,pluginConfig:t.plugin_config??void 0}})),await this.#s[e]}async getGraphQLSchema(e){return null==this.#m[e]&&(this.#m[e]=this.#I(e).then(t=>{let i;if(null!=t.pluginConfig){let e={},s={},n={},a={},r={};for(let[i,o]of Object.entries(t.pluginConfig)){let t=this.#l.get(i);if(null!=t){let i=t(o);if(Object.assign(e,i.resolvers.queryFields??{}),Object.assign(s,i.resolvers.mutationFields??{}),Object.assign(n,i.resolvers.subscriptionFields??{}),Object.assign(r,i.resolvers.nodeResolvers??{}),null!=i.resolvers.typeFields)for(let[e,t]of Object.entries(i.resolvers.typeFields))a[e]={...a[e],...t}}}i={queryFields:e,mutationFields:s,subscriptionFields:n,typeFields:a,nodeResolvers:r}}let s=l({...t,extensionResolvers:i});return this.#r.debug("cached schema for graph {id}",{id:e}),s}).catch(t=>{throw delete this.#m[e],t})),await this.#m[e]}async #M(e,t){let i=t??x(e.text);if(null==i.definitions[0])throw Error("Missing GraphQL document definition");return{schema:await this.getGraphQLSchema(e.graphID),document:i,contextValue:this.#w(e),variableValues:e.variables}}#E(e){let t=e.definitions[0];return null!=t&&t.kind===D.OPERATION_DEFINITION?t.operation:"query"}async #k(e,t){let i=await G(this.#o,e,{viewerDID:t});return"issues"in i&&null!=i.issues&&i.issues.length>0?{errors:i.issues.map(e=>new b(e.message,{extensions:null!=e.path?{path:e.path}:void 0}))}:null}async listGraphs(){return{graphs:(await this.#t.listGraphs()).map(e=>({id:e.id,name:e.name}))}}async loadGraph(e){let t=await this.#I(e.id);return{aliases:t.aliases,record:t.record,extensionSDL:t.extensionSDL}}async deployGraph(e){let t,i,s={};for(let t of e.clusters)Object.assign(s,y(t));let n=v.fromClusters({clusters:s});if(null!=e.plugins){i=e.plugins;let s=[];for(let[e,t]of Object.entries(i)){let i=this.#l.get(e);if(null==i)throw Error(`Plugin "${e}" not found or does not provide schema extensions`);let n=i(t);s.push(n.sdl)}s.length>0&&(t=s.join("\n"))}let a=await this.#t.createGraph({id:e.id??this.#h.getRandomID(),name:e.name,record:n.record,extensionSDL:t,pluginConfig:i}),r=n.toJSON();return this.#s[a]=Promise.resolve({...r,extensionSDL:t,pluginConfig:i}),delete this.#m[a],delete this.#p[a],this.#r.info("deployed graph {id}",{id:a}),{id:a,...r}}async #G(e){for(let{document:t,mutation:i,authorDID:s}of e)"set"===i.typ?await this.#i.emit("engine:document:created",{documentID:t.id,modelID:t.model,viewerDID:s}):await this.#i.emit("engine:document:updated",{documentID:t.id,modelID:t.model,viewerDID:s}),await this.#i.emit("engine:mutation:applied",{type:"set"===i.typ?"create":"update",documentID:t.id,viewerDID:s})}async #b(e){let{token:i,graphID:s}=e,n=t(P,(await a(i)).payload);this.#p[s]??={};let r={db:this.#t,validators:this.#p[s],hlc:this.#n},o=await h(r,n),l=k(i),u=n.iss;await this.#t.insertMutationLogEntry({mutation_hash:l,model_id:o.model,document_id:o.id,author_did:u,hlc:n.hlc,mutation_jwt:i,status:"applied"});let c={document:o,mutation:n,authorDID:u,hash:l};return await this.#G([c]),c}async #D(e){let{tokens:i,graphID:s}=e;this.#p[s]??={};let n=await Promise.all(i.map(async e=>{let i=t(P,(await a(e)).payload);return{token:e,mutation:i}})),r=[];return await this.#t.withTransaction(async e=>{for(let{token:t,mutation:i}of n){let n={db:e,validators:this.#p[s],hlc:this.#n},a=await h(n,i),o=k(t);await e.insertMutationLogEntry({mutation_hash:o,model_id:a.model,document_id:a.id,author_did:i.iss,hlc:i.hlc,mutation_jwt:t,status:"applied"}),r.push({document:a,mutation:i,authorDID:i.iss,hash:o})}}),await this.#G(r),{results:r}}async #f(e){let t=x(e.text),i=this.#E(t),s=await this.#k(i,e.viewerDID??this.#a.id);if(null!=s)return s;let n=await this.#M(e,t);return await f(n)}async #y(e){let t=await this.#M(e);return await I(t)}async queryGraph(e){let t=x(e.text);if("mutation"===this.#E(t))throw Error("queryGraph() does not accept mutation operations. Use mutateGraph() instead.");return await this.#f({graphID:e.id,text:e.text,variables:e.variables??{},viewerDID:e.viewerDID})}async mutateGraph(e){if(!s(this.#a))throw Error("mutateGraph requires a SigningIdentity");let t=this.#a,i=e.id,a=async e=>{let s=n(await t.signToken(e)),{document:a}=await this.#b({token:s,graphID:i});return a},r=g({issuer:t.id,hlc:this.#n,getRandomValues:this.#h.getRandomValues,processSetMutation:async e=>await a(e),processChangeMutation:async e=>await a(e)});return await this.#f({graphID:i,text:e.text,variables:e.variables??{},viewerDID:e.viewerDID??t.id,contextExtensions:{executeCreateMutation:async e=>await r.createDocument({modelID:e.modelID,data:e.data}),executeSetMutation:async e=>await r.setDocument({modelID:e.modelID,unique:e.unique,data:e.data}),executeUpdateMutation:async e=>await r.updateDocument({docID:e.input.id,patch:p(e.input.patch)}),executeRemoveMutation:async e=>{await r.removeDocument({docID:e.id})}}})}async subscribeToGraph(e){return await this.#y({graphID:e.id,text:e.text,variables:e.variables??{},viewerDID:e.viewerDID})}async getAPI(e){return await this.#d.getAPI(e)}}
1
+ import{createRuntime as e}from"@enkaku/runtime";import{asType as t,createValidator as i}from"@enkaku/schema";import{isSigningIdentity as s,stringifyToken as r,verifyToken as a}from"@enkaku/token";import{KubunDB as n}from"@kubun/db";import{createReadContext as o,createSchema as l}from"@kubun/graphql";import{HLC as u}from"@kubun/hlc";import{getKubunLogger as h}from"@kubun/logger";import{applyMutation as c,convertPatchInput as d,createMutationOperations as p}from"@kubun/mutation";import{clusterToRecord as g,documentMutation as m,GraphModel as y}from"@kubun/protocol";import{GRAPH_STORE as v,getGraphStore as w,graphStoreDefinition as f}from"@kubun/store-graph";import{execute as x,GraphQLError as b,Kind as D,parse as I,subscribe as M}from"graphql";import{EngineEventBus as E}from"./events.js";import{computeMutationHash as G}from"./mutation-hash.js";import{runPolicies as k}from"./policies.js";import{createRegistry as O}from"./registry.js";let S=i(m);function j(){throw Error("Graph mutations require mutateGraph() — the core context does not sign or log writes. Use KubunEngine.mutateGraph() instead.")}function q(){throw Error("Access control mutations are not supported by the engine core — plugin override required.")}function C(){throw Error("Transaction mutations are not supported by the engine core — plugin override required (see plugin-rpc).")}let F={executeCreateMutation:j,executeSetMutation:j,executeUpdateMutation:j,executeRemoveMutation:j,executeSetModelAccessDefaults:q,executeRemoveModelAccessDefaults:q,executeSetDocumentAccessOverride:q,executeRemoveDocumentAccessOverride:q,beginTransaction:C,commitTransaction:C,rollbackTransaction:C};export class KubunEngine{#e=[];#t;#i;#s={};#r;#a;#n;#o={};#l=new Map;#u=[];#h;#c;#d={};#p={};constructor(t){this.#t=t.db instanceof n?t.db:new n({adapter:t.db}),this.#t.register(f),this.#a=t.identity,this.#r=new u({nodeID:t.identity.id}),this.#n=t.logger??h("engine"),this.#i=t.eventBus??new E({logger:this.#n.getChild("events")}),this.#c=e(t.runtime),this.#h=O();let i=t.plugins??[],s={engine:this,graph:{execute:e=>this.#g(e),subscribe:e=>this.#m(e),applyVerifiedMutation:e=>this.#y(e),applyVerifiedMutations:e=>this.#v(e)},db:this.#t,runtime:this.#c,identity:this.#a,eventBus:this.#i,hlc:this.#r,getLogger:e=>this.#n.getChild(e)};for(let e of i){let t=e(s);this.#u.push(t),null!=t.api&&this.#h.registerPlugin(t.name,t.api)}for(let e of(this.#h.closeGate(),this.#u))if(null!=e.schemaExtension&&this.#l.set(e.name,e.schemaExtension),null!=e.createContextFactory&&this.#e.push(e.createContextFactory()),null!=e.policies)for(let[t,i]of Object.entries(e.policies)){null==this.#o[t]&&(this.#o[t]={sync:[],async:[]});let e=this.#o[t];null!=i.sync&&(e.sync=[...e.sync??[],...i.sync]),null!=i.async&&(e.async=[...e.async??[],...i.async])}this.#n.info("engine initialized with {count} plugins",{count:this.#u.length})}get did(){return this.#a.id}get identity(){return this.#a}get eventBus(){return this.#i}async dispose(){await Promise.all(this.#u.filter(e=>null!=e.dispose).map(e=>e.dispose?.()))}getGraphQLSource(e){let{graphID:t}=e,i=e.getViewerDID??(()=>this.#a.id);return{query:async e=>await this.queryGraph({id:t,text:e.text,variables:e.variables,viewerDID:i()??void 0}),mutate:async e=>await this.mutateGraph({id:t,text:e.text,variables:e.variables,viewerDID:i()??void 0}),subscribe:e=>this.#m({graphID:t,text:e.text,variables:e.variables,viewerDID:i()??void 0})}}registerContextFactory(e){this.#e.push(e)}async #w(){return await w(this.#t)}async #f(e){var t;let i,s=e.viewerDID??this.#a.id,r=this.#x(s),a=e.stores??this.#t,n={};for(let e of this.#e)n={...n,...e(r,a)};null!=e.contextExtensions&&(n={...n,...e.contextExtensions});let l=e.graphID;return this.#p[l]??={},i={...o({store:(t={store:await w(a),viewerDID:s,eventBus:this.#i,contextExtensions:Object.keys(n).length>0?n:void 0}).store,viewerDID:t.viewerDID,events:t.eventBus}),...F},null!=t.contextExtensions?{...i,...t.contextExtensions}:i}#x(e){return{viewerDID:e}}async #b(e){return null==this.#s[e]&&(this.#s[e]=this.#w().then(async t=>{let i=await t.getGraph(e);if(null==i)throw this.#n.warn("graph {id} not found",{id:e}),delete this.#s[e],Error(`Graph not found: ${e}`);return this.#n.debug("cached model for graph {id}",{id:e}),{aliases:i.aliases,record:i.record,extensionSDL:i.extension_sdl??void 0,pluginConfig:i.plugin_config??void 0}})),await this.#s[e]}async getGraphQLSchema(e){return null==this.#d[e]&&(this.#d[e]=this.#b(e).then(t=>{let i;if(null!=t.pluginConfig){let e={},s={},r={},a={},n={};for(let[i,o]of Object.entries(t.pluginConfig)){let t=this.#l.get(i);if(null!=t){let i=t(o);if(Object.assign(e,i.resolvers.queryFields??{}),Object.assign(s,i.resolvers.mutationFields??{}),Object.assign(r,i.resolvers.subscriptionFields??{}),Object.assign(n,i.resolvers.nodeResolvers??{}),null!=i.resolvers.typeFields)for(let[e,t]of Object.entries(i.resolvers.typeFields))a[e]={...a[e],...t}}}i={queryFields:e,mutationFields:s,subscriptionFields:r,typeFields:a,nodeResolvers:n}}let s=l({...t,extensionResolvers:i});return this.#n.debug("cached schema for graph {id}",{id:e}),s}).catch(t=>{throw delete this.#d[e],t})),await this.#d[e]}async #D(e,t){let i=t??I(e.text);if(null==i.definitions[0])throw Error("Missing GraphQL document definition");return{schema:await this.getGraphQLSchema(e.graphID),document:i,contextValue:await this.#f(e),variableValues:e.variables}}#I(e){let t=e.definitions[0];return null!=t&&t.kind===D.OPERATION_DEFINITION?t.operation:"query"}async #M(e,t){let i=await k(this.#o,e,{viewerDID:t});return"issues"in i&&null!=i.issues&&i.issues.length>0?{errors:i.issues.map(e=>new b(e.message,{extensions:null!=e.path?{path:e.path}:void 0}))}:null}async listGraphs(){let e=await this.#w();return{graphs:(await e.listGraphs()).map(e=>({id:e.id,name:e.name}))}}async loadGraph(e){let t=await this.#b(e.id);return{aliases:t.aliases,record:t.record,extensionSDL:t.extensionSDL}}async deployGraph(e){let t,i,s={};for(let t of e.clusters)Object.assign(s,g(t));let r=y.fromClusters({clusters:s});if(null!=e.plugins){i=e.plugins;let s=[];for(let[e,t]of Object.entries(i)){let i=this.#l.get(e);if(null==i)throw Error(`Plugin "${e}" not found or does not provide schema extensions`);let r=i(t);s.push(r.sdl)}s.length>0&&(t=s.join("\n"))}let a=await this.#w(),n=await a.createGraph({id:e.id??this.#c.getRandomID(),name:e.name,record:r.record,extensionSDL:t,pluginConfig:i}),o=r.toJSON();return this.#s[n]=Promise.resolve({...o,extensionSDL:t,pluginConfig:i}),delete this.#d[n],delete this.#p[n],this.#n.info("deployed graph {id}",{id:n}),{id:n,...o}}async #E(e){for(let{document:t,mutation:i,authorDID:s}of e){let e="set"===i.typ?"create":"update",r={type:e,document:t,previous:{data:null},getCursor:()=>t.id};await this.#i.emit("document:saved",r),"create"===e?await this.#i.emit("engine:document:created",{documentID:t.id,modelID:t.model,viewerDID:s}):await this.#i.emit("engine:document:updated",{documentID:t.id,modelID:t.model,viewerDID:s}),await this.#i.emit("engine:mutation:applied",{type:e,documentID:t.id,viewerDID:s})}}async #y(e,i){let{token:s,graphID:r}=e,n=t(S,(await a(s)).payload);this.#p[r]??={};let o=null!=i?await w(i):await this.#w(),l={store:o,validators:this.#p[r],hlc:this.#r},u=await c(l,n),h=G(s),d=n.iss;await o.insertMutationLogEntry({mutation_hash:h,model_id:u.model,document_id:u.id,author_did:d,hlc:n.hlc,mutation_jwt:s,status:"applied"});let p={document:u,mutation:n,authorDID:d,hash:h};return null==i&&await this.#E([p]),p}async #v(e){let{tokens:i,graphID:s}=e;this.#p[s]??={};let r=await Promise.all(i.map(async e=>{let i=t(S,(await a(e)).payload);return{token:e,mutation:i}})),n=[];return await this.#t.withTransaction(async e=>{let t=await e.getStore(v);for(let{token:e,mutation:i}of r){let r={store:t,validators:this.#p[s],hlc:this.#r},a=await c(r,i),o=G(e);await t.insertMutationLogEntry({mutation_hash:o,model_id:a.model,document_id:a.id,author_did:i.iss,hlc:i.hlc,mutation_jwt:e,status:"applied"}),n.push({document:a,mutation:i,authorDID:i.iss,hash:o})}}),await this.#E(n),{results:n}}async #g(e){let t=I(e.text),i=this.#I(t),s=await this.#M(i,e.viewerDID??this.#a.id);if(null!=s)return s;let r=await this.#D(e,t);return await x(r)}async #m(e){let t=await this.#D(e);return await M(t)}async queryGraph(e){let t=I(e.text);if("mutation"===this.#I(t))throw Error("queryGraph() does not accept mutation operations. Use mutateGraph() instead.");return await this.#g({graphID:e.id,text:e.text,variables:e.variables??{},viewerDID:e.viewerDID})}async mutateGraph(e){if(!s(this.#a))throw Error("mutateGraph requires a SigningIdentity");let t=this.#a,i=e.id,a=[],n=await this.#t.withTransaction(async s=>{let n=async e=>{let n=r(await t.signToken(e)),o=await this.#y({token:n,graphID:i},s);return a.push(o),o.document},o=p({issuer:t.id,hlc:this.#r,getRandomValues:this.#c.getRandomValues,processSetMutation:e=>n(e),processChangeMutation:e=>n(e)});return await this.#g({graphID:i,text:e.text,variables:e.variables??{},viewerDID:e.viewerDID??t.id,stores:s,contextExtensions:{executeCreateMutation:async e=>await o.createDocument({modelID:e.modelID,data:e.data}),executeSetMutation:async e=>await o.setDocument({modelID:e.modelID,unique:e.unique,data:e.data}),executeUpdateMutation:async e=>await o.updateDocument({docID:e.input.id,patch:d(e.input.patch)}),executeRemoveMutation:async e=>{await o.removeDocument({docID:e.id})}}})});return await this.#E(a),n}async subscribeToGraph(e){return await this.#m({graphID:e.id,text:e.text,variables:e.variables??{},viewerDID:e.viewerDID})}async getAPI(e){return await this.#h.getAPI(e)}}
package/lib/executor.d.ts CHANGED
@@ -14,5 +14,4 @@ export type Engine = Omit<GraphsProvider, 'queryGraph' | 'mutateGraph' | 'subscr
14
14
  mutateGraph<Data extends Record<string, unknown> = Record<string, unknown>>(params: EngineGraphParams): Promise<ExecuteGraphResult<Data>>;
15
15
  subscribeToGraph<Data extends Record<string, unknown> = Record<string, unknown>>(params: EngineGraphParams): Promise<AsyncGenerator<ExecuteGraphResult<Data>> | ExecuteGraphResult<Data>>;
16
16
  getAPI: GetAPI;
17
- ready: Promise<void>;
18
17
  };
package/lib/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export type { AccessChecker, AccessLevel, AccessPermissions, AccessRule, DefaultAccessLevel, } from './access-control.js';
1
+ export type { AccessChecker, AccessControlDB, AccessLevel, AccessPermissions, AccessRule, DefaultAccessLevel, } from './access-control.js';
2
2
  export { createAccessChecker, parseDocumentAccessPermissions, validateDIDs, } from './access-control.js';
3
- export type { ApplyVerifiedMutationParams, ApplyVerifiedMutationResult, ApplyVerifiedMutationsParams, ApplyVerifiedMutationsResult, ContextFactory, EngineParams, ExecuteParams, ExecutionContext, PluginMigrationContext, PluginMigrations, TransactionContext, } from './engine.js';
3
+ export type { ApplyVerifiedMutationParams, ApplyVerifiedMutationResult, ApplyVerifiedMutationsParams, ApplyVerifiedMutationsResult, ContextFactory, EngineParams, ExecuteParams, ExecutionContext, } from './engine.js';
4
4
  export { KubunEngine } from './engine.js';
5
5
  export type { EngineEvents } from './engine-events.js';
6
6
  export { EngineEventBus } from './events.js';
package/lib/plugin.d.ts CHANGED
@@ -5,7 +5,7 @@ import type { ExtensionResolvers } from '@kubun/graphql';
5
5
  import type { HLC } from '@kubun/hlc';
6
6
  import type { Logger } from '@kubun/logger';
7
7
  import type { ExecutionResult } from 'graphql';
8
- import type { ApplyVerifiedMutationParams, ApplyVerifiedMutationResult, ApplyVerifiedMutationsParams, ApplyVerifiedMutationsResult, ContextFactory, ExecuteParams, PluginMigrations } from './engine.js';
8
+ import type { ApplyVerifiedMutationParams, ApplyVerifiedMutationResult, ApplyVerifiedMutationsParams, ApplyVerifiedMutationsResult, ContextFactory, ExecuteParams } from './engine.js';
9
9
  import type { EngineEventBus } from './events.js';
10
10
  import type { Engine } from './executor.js';
11
11
  import type { PolicyGateMap } from './policies.js';
@@ -48,7 +48,6 @@ export type PluginFactoryParams = {
48
48
  */
49
49
  export type KubunPlugin = {
50
50
  name: string;
51
- migrations?: PluginMigrations;
52
51
  policies?: PolicyGateMap;
53
52
  schemaExtension?: (config: Record<string, unknown>) => SchemaExtension;
54
53
  events?: Record<string, string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubun/engine",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "license": "see LICENSE.md",
5
5
  "keywords": [],
6
6
  "type": "module",
@@ -15,7 +15,7 @@
15
15
  ],
16
16
  "sideEffects": false,
17
17
  "dependencies": {
18
- "@enkaku/async": "^0.14.0",
18
+ "@enkaku/async": "^0.14.1",
19
19
  "@enkaku/capability": "^0.14.0",
20
20
  "@enkaku/event": "^0.14.1",
21
21
  "@enkaku/runtime": "^0.14.0",
@@ -23,16 +23,19 @@
23
23
  "@enkaku/token": "0.14.1",
24
24
  "@noble/hashes": "^2.2.0",
25
25
  "graphql": "^16.13.2",
26
- "kysely": "^0.28.16",
27
- "@kubun/db": "^0.8.0",
28
- "@kubun/db-adapter": "^0.8.0",
29
- "@kubun/protocol": "^0.8.0",
30
- "@kubun/mutation": "^0.8.0",
31
- "@kubun/graphql": "^0.8.0",
26
+ "@kubun/db": "^0.8.1",
27
+ "@kubun/db-adapter": "^0.8.1",
28
+ "@kubun/graphql": "^0.8.1",
29
+ "@kubun/hlc": "^0.8.0",
32
30
  "@kubun/logger": "^0.8.0",
33
- "@kubun/hlc": "^0.8.0"
31
+ "@kubun/mutation": "^0.8.1",
32
+ "@kubun/store-graph": "^0.8.1",
33
+ "@kubun/protocol": "^0.8.1"
34
34
  },
35
35
  "devDependencies": {
36
+ "@testcontainers/postgresql": "^11.14.0",
37
+ "@kubun/db-postgres": "^0.8.1",
38
+ "@kubun/store-p2p": "^0.8.0",
36
39
  "@kubun/id": "^0.8.0",
37
40
  "@kubun/test-utils": "^0.8.0"
38
41
  },