@kubun/engine 0.8.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/LICENSE.md +57 -0
- package/lib/access-control.d.ts +35 -0
- package/lib/access-control.js +1 -0
- package/lib/engine-events.d.ts +27 -0
- package/lib/engine-events.js +1 -0
- package/lib/engine.d.ts +122 -0
- package/lib/engine.js +1 -0
- package/lib/events.d.ts +55 -0
- package/lib/events.js +1 -0
- package/lib/executor.d.ts +18 -0
- package/lib/executor.js +1 -0
- package/lib/index.d.ts +13 -0
- package/lib/index.js +1 -0
- package/lib/mutation-hash.d.ts +1 -0
- package/lib/mutation-hash.js +1 -0
- package/lib/plugin.d.ts +59 -0
- package/lib/plugin.js +1 -0
- package/lib/policies.d.ts +73 -0
- package/lib/policies.js +1 -0
- package/lib/registry.d.ts +7 -0
- package/lib/registry.js +1 -0
- package/package.json +49 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# The Prosperity Public License 3.0.0
|
|
2
|
+
|
|
3
|
+
Contributor: Paul Le Cam
|
|
4
|
+
|
|
5
|
+
Source Code: https://github.com/PaulLeCam/kubun
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
This license allows you to use and share this software for noncommercial purposes for free and to try this software for commercial purposes for thirty days.
|
|
10
|
+
|
|
11
|
+
## Agreement
|
|
12
|
+
|
|
13
|
+
In order to receive this license, you have to agree to its rules. Those rules are both obligations under that agreement and conditions to your license. Don't do anything with this software that triggers a rule you can't or won't follow.
|
|
14
|
+
|
|
15
|
+
## Notices
|
|
16
|
+
|
|
17
|
+
Make sure everyone who gets a copy of any part of this software from you, with or without changes, also gets the text of this license and the contributor and source code lines above.
|
|
18
|
+
|
|
19
|
+
## Commercial Trial
|
|
20
|
+
|
|
21
|
+
Limit your use of this software for commercial purposes to a thirty-day trial period. If you use this software for work, your company gets one trial period for all personnel, not one trial per person.
|
|
22
|
+
|
|
23
|
+
## Contributions Back
|
|
24
|
+
|
|
25
|
+
Developing feedback, changes, or additions that you contribute back to the contributor on the terms of a standardized public software license such as [the Blue Oak Model License 1.0.0](https://blueoakcouncil.org/license/1.0.0), [the Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.html), [the MIT license](https://spdx.org/licenses/MIT.html), or [the two-clause BSD license](https://spdx.org/licenses/BSD-2-Clause.html) doesn't count as use for a commercial purpose.
|
|
26
|
+
|
|
27
|
+
## Personal Uses
|
|
28
|
+
|
|
29
|
+
Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, doesn't count as use for a commercial purpose.
|
|
30
|
+
|
|
31
|
+
## Noncommercial Organizations
|
|
32
|
+
|
|
33
|
+
Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution doesn't count as use for a commercial purpose regardless of the source of funding or obligations resulting from the funding.
|
|
34
|
+
|
|
35
|
+
## Defense
|
|
36
|
+
|
|
37
|
+
Don't make any legal claim against anyone accusing this software, with or without changes, alone or with other technology, of infringing any patent.
|
|
38
|
+
|
|
39
|
+
## Copyright
|
|
40
|
+
|
|
41
|
+
The contributor licenses you to do everything with this software that would otherwise infringe their copyright in it.
|
|
42
|
+
|
|
43
|
+
## Patent
|
|
44
|
+
|
|
45
|
+
The contributor licenses you to do everything with this software that would otherwise infringe any patents they can license or become able to license.
|
|
46
|
+
|
|
47
|
+
## Reliability
|
|
48
|
+
|
|
49
|
+
The contributor can't revoke this license.
|
|
50
|
+
|
|
51
|
+
## Excuse
|
|
52
|
+
|
|
53
|
+
You're excused for unknowingly breaking [Notices](#notices) if you take all practical steps to comply within thirty days of learning you broke the rule.
|
|
54
|
+
|
|
55
|
+
## No Liability
|
|
56
|
+
|
|
57
|
+
***As far as the law allows, this software comes as is, without any warranty or condition, and the contributor won't be liable to anyone for any damages related to this software or this license, under any kind of legal claim.***
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { KubunDB } from '@kubun/db';
|
|
2
|
+
import type { DocumentNode } from '@kubun/protocol';
|
|
3
|
+
export type AccessLevel = 'only_owner' | 'anyone' | 'allowed_dids';
|
|
4
|
+
export type AccessRule = {
|
|
5
|
+
level: AccessLevel;
|
|
6
|
+
allowedDIDs: Array<string> | null;
|
|
7
|
+
allowedCircles: Array<string> | null;
|
|
8
|
+
};
|
|
9
|
+
export type AccessPermissions = {
|
|
10
|
+
read?: AccessRule;
|
|
11
|
+
write?: AccessRule;
|
|
12
|
+
};
|
|
13
|
+
export type DefaultAccessLevel = {
|
|
14
|
+
read: AccessLevel;
|
|
15
|
+
write: 'only_owner' | 'allowed_dids';
|
|
16
|
+
};
|
|
17
|
+
export type AccessChecker = (doc: DocumentNode, permissionType: 'read' | 'write') => Promise<boolean>;
|
|
18
|
+
/**
|
|
19
|
+
* Parse and validate access permissions from document data.
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseDocumentAccessPermissions(data: unknown): AccessPermissions | null;
|
|
22
|
+
/**
|
|
23
|
+
* Validate that DIDs have the correct format.
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateDIDs(dids: Array<string>): void;
|
|
26
|
+
/**
|
|
27
|
+
* Create an access checker function bound to specific viewer, delegation tokens,
|
|
28
|
+
* database instance, and server default access level.
|
|
29
|
+
*/
|
|
30
|
+
export declare function createAccessChecker(params: {
|
|
31
|
+
viewerDID: string | undefined;
|
|
32
|
+
delegationTokens?: Array<string>;
|
|
33
|
+
db: KubunDB;
|
|
34
|
+
defaultAccessLevel: DefaultAccessLevel;
|
|
35
|
+
}): AccessChecker;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{checkCapability as e}from"@enkaku/capability";export function parseDocumentAccessPermissions(e){try{if(!e||"object"!=typeof e||!("accessPermissions"in e))return null;let l=e.accessPermissions;if("object"!=typeof l)return null;return l}catch{return null}}export function validateDIDs(e){for(let l of e)if(!l.startsWith("did:"))throw Error(`Invalid DID format: ${l}`)}async function l(e,l,r,n,t,o){let a=parseDocumentAccessPermissions(e.data);if(a?.[n]){var i;let e=a[n];if(e&&(i=e.level,"read"===n?"only_owner"===i||"anyone"===i||"allowed_dids"===i:"only_owner"===i||"allowed_dids"===i))return{level:e.level,allowedDIDs:e.allowedDIDs||null,allowedCircles:e.allowedCircles||null}}let s=await t.getUserModelAccessDefault(r,l,n);return s?{level:s.level,allowedDIDs:s.allowedDIDs,allowedCircles:null}:{level:o[n],allowedDIDs:null,allowedCircles:null}}async function r(l,r,n,t,o){if(!o||0===o.length)return!1;let a=[`urn:kubun:document:${n.id}`,`urn:kubun:model:${n.model}`,"*"],i=`document/${t}`;for(let n of a)try{return await e({act:i,res:n},{iss:l,sub:r,cap:o}),!0}catch{}for(let n of o)for(let t of a)try{return await e({act:i,res:t},{iss:l,sub:r,cap:n}),!0}catch{}return!1}async function n(e,n,t,o,a,i){if(!n.owner)throw Error("Document missing owner field");if(e===n.owner)return!0;let s=await l(n,n.model,n.owner,t,o,a);if("anyone"===s.level)return!0;if(!e)return!1;if("only_owner"===s.level)return await r(e,n.owner,n,t,i);if("allowed_dids"===s.level){let l=s.allowedDIDs||[];if(l.includes(e)||"read"===t&&null!=s.allowedCircles&&s.allowedCircles.length>0&&await o.isMemberOfAnyCircle(e,s.allowedCircles))return!0;for(let o of l)if(await r(e,o,n,t,i))return!0}return!1}export function createAccessChecker(e){return async(l,r)=>await n(e.viewerDID,l,r,e.db,e.defaultAccessLevel,e.delegationTokens)}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event types emitted by the engine during document operations.
|
|
3
|
+
*
|
|
4
|
+
* These events are emitted after successful mutations and can be
|
|
5
|
+
* observed by plugins or application code via the EngineEventBus.
|
|
6
|
+
*/
|
|
7
|
+
export type EngineEvents = {
|
|
8
|
+
'engine:document:created': {
|
|
9
|
+
documentID: string;
|
|
10
|
+
modelID: string;
|
|
11
|
+
viewerDID: string;
|
|
12
|
+
};
|
|
13
|
+
'engine:document:updated': {
|
|
14
|
+
documentID: string;
|
|
15
|
+
modelID: string;
|
|
16
|
+
viewerDID: string;
|
|
17
|
+
};
|
|
18
|
+
'engine:document:removed': {
|
|
19
|
+
documentID: string;
|
|
20
|
+
viewerDID: string;
|
|
21
|
+
};
|
|
22
|
+
'engine:mutation:applied': {
|
|
23
|
+
type: 'create' | 'update' | 'remove';
|
|
24
|
+
documentID: string;
|
|
25
|
+
viewerDID: string;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{};
|
package/lib/engine.d.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { type Runtime } from '@enkaku/runtime';
|
|
2
|
+
import type { Identity } from '@enkaku/token';
|
|
3
|
+
import { type DBParams, KubunDB } from '@kubun/db';
|
|
4
|
+
import type { Adapter } from '@kubun/db-adapter';
|
|
5
|
+
import { type Logger } from '@kubun/logger';
|
|
6
|
+
import type { DeployGraphParams, DeployGraphResult, DocumentNode, ExecuteGraphResult, ListGraphResult, LoadGraphParams, LoadGraphResult } from '@kubun/protocol';
|
|
7
|
+
import { type DocumentMutation } from '@kubun/protocol';
|
|
8
|
+
import type { GraphQLSchema } from 'graphql';
|
|
9
|
+
import type { Migration, Transaction } from 'kysely';
|
|
10
|
+
import type { EngineEvents } from './engine-events.js';
|
|
11
|
+
import { EngineEventBus } from './events.js';
|
|
12
|
+
import type { Engine, EngineGraphParams, GraphQLSource, GraphQLSourceParams } from './executor.js';
|
|
13
|
+
import type { KubunPlugin, PluginFactoryParams } from './plugin.js';
|
|
14
|
+
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
|
+
/**
|
|
34
|
+
* Per-request execution context provided to plugin operations.
|
|
35
|
+
* Only carries what's per-request — shared resources (identity, hlc, logger, etc.)
|
|
36
|
+
* are already captured in the plugin closure from PluginFactoryParams.
|
|
37
|
+
*/
|
|
38
|
+
export type ExecutionContext = {
|
|
39
|
+
viewerDID: string;
|
|
40
|
+
/** On-demand transaction factory — not created until called */
|
|
41
|
+
transaction<R>(fn: (tx: TransactionContext) => Promise<R>): Promise<R>;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* A function that produces per-request context fields.
|
|
45
|
+
* Called by the engine for each execute()/subscribe() invocation.
|
|
46
|
+
*/
|
|
47
|
+
export type ContextFactory = (executionContext: ExecutionContext) => Record<string, unknown>;
|
|
48
|
+
export type EngineParams = {
|
|
49
|
+
db: DBParams | KubunDB;
|
|
50
|
+
identity: Identity;
|
|
51
|
+
logger?: Logger;
|
|
52
|
+
runtime?: Partial<Runtime>;
|
|
53
|
+
/** Plugin factories — called with shared params, return plugin descriptors. */
|
|
54
|
+
plugins?: Array<(params: PluginFactoryParams) => KubunPlugin>;
|
|
55
|
+
eventBus?: EngineEventBus<EngineEvents>;
|
|
56
|
+
};
|
|
57
|
+
export type ApplyVerifiedMutationParams = {
|
|
58
|
+
/** The signed mutation JWT token */
|
|
59
|
+
token: string;
|
|
60
|
+
/** The graph ID (needed to resolve validators for the document model) */
|
|
61
|
+
graphID: string;
|
|
62
|
+
};
|
|
63
|
+
export type ApplyVerifiedMutationResult = {
|
|
64
|
+
/** The resulting document after mutation */
|
|
65
|
+
document: DocumentNode;
|
|
66
|
+
/** The verified mutation payload */
|
|
67
|
+
mutation: DocumentMutation;
|
|
68
|
+
/** The DID of the mutation author (from JWT issuer) */
|
|
69
|
+
authorDID: string;
|
|
70
|
+
/** BLAKE3 hash of the mutation JWT */
|
|
71
|
+
hash: string;
|
|
72
|
+
};
|
|
73
|
+
export type ApplyVerifiedMutationsParams = {
|
|
74
|
+
tokens: Array<string>;
|
|
75
|
+
graphID: string;
|
|
76
|
+
};
|
|
77
|
+
export type ApplyVerifiedMutationsResult = {
|
|
78
|
+
results: Array<ApplyVerifiedMutationResult>;
|
|
79
|
+
};
|
|
80
|
+
export type ExecuteParams = {
|
|
81
|
+
graphID: string;
|
|
82
|
+
text: string;
|
|
83
|
+
variables?: Record<string, unknown>;
|
|
84
|
+
viewerDID?: string;
|
|
85
|
+
contextExtensions?: Record<string, unknown>;
|
|
86
|
+
};
|
|
87
|
+
export declare class KubunEngine implements Engine {
|
|
88
|
+
#private;
|
|
89
|
+
constructor(params: EngineParams);
|
|
90
|
+
get did(): string;
|
|
91
|
+
get identity(): Identity;
|
|
92
|
+
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
|
+
/**
|
|
99
|
+
* Dispose the engine by shutting down all plugins in parallel.
|
|
100
|
+
*/
|
|
101
|
+
dispose(): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Return a GraphQLSource bound to a specific graph (and optionally a viewer).
|
|
104
|
+
* The returned object delegates query/mutate/subscribe to the engine, injecting
|
|
105
|
+
* the graphID and viewerDID from the source params on each call.
|
|
106
|
+
*/
|
|
107
|
+
getGraphQLSource(params: GraphQLSourceParams): GraphQLSource;
|
|
108
|
+
/**
|
|
109
|
+
* Register a context factory that contributes per-request fields to the
|
|
110
|
+
* GraphQL execution context. Each factory is called on every execute()/subscribe()
|
|
111
|
+
* invocation with the request info.
|
|
112
|
+
*/
|
|
113
|
+
registerContextFactory(factory: ContextFactory): void;
|
|
114
|
+
getGraphQLSchema(id: string): Promise<GraphQLSchema>;
|
|
115
|
+
listGraphs(): Promise<ListGraphResult>;
|
|
116
|
+
loadGraph(params: LoadGraphParams): Promise<LoadGraphResult>;
|
|
117
|
+
deployGraph(params: DeployGraphParams): Promise<DeployGraphResult>;
|
|
118
|
+
queryGraph<Data extends Record<string, unknown> = Record<string, unknown>>(params: EngineGraphParams): Promise<ExecuteGraphResult<Data>>;
|
|
119
|
+
mutateGraph<Data extends Record<string, unknown> = Record<string, unknown>>(params: EngineGraphParams): Promise<ExecuteGraphResult<Data>>;
|
|
120
|
+
subscribeToGraph<Data extends Record<string, unknown> = Record<string, unknown>>(params: EngineGraphParams): Promise<AsyncGenerator<ExecuteGraphResult<Data>> | ExecuteGraphResult<Data>>;
|
|
121
|
+
getAPI<T extends Record<string, unknown> = Record<string, unknown>>(name: string): Promise<T>;
|
|
122
|
+
}
|
package/lib/engine.js
ADDED
|
@@ -0,0 +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)}}
|
package/lib/events.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type UnsubscribeFunction } from '@enkaku/event';
|
|
2
|
+
import type { Logger } from '@kubun/logger';
|
|
3
|
+
/**
|
|
4
|
+
* Map of event names to their data types.
|
|
5
|
+
* Uses `Record<string, unknown>` so any namespaced string key is valid.
|
|
6
|
+
*/
|
|
7
|
+
type EventMap = Record<string, unknown>;
|
|
8
|
+
/**
|
|
9
|
+
* Transaction-aware event bus built on top of `@enkaku/event`.
|
|
10
|
+
*
|
|
11
|
+
* Events emitted outside a transaction fire immediately.
|
|
12
|
+
* Events emitted inside a transaction are buffered until commit,
|
|
13
|
+
* and discarded on rollback.
|
|
14
|
+
*/
|
|
15
|
+
export declare class EngineEventBus<TEvents extends EventMap = EventMap> {
|
|
16
|
+
#private;
|
|
17
|
+
constructor(options?: {
|
|
18
|
+
logger?: Logger;
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* Subscribe to a namespaced event.
|
|
22
|
+
* Returns an unsubscribe function.
|
|
23
|
+
*/
|
|
24
|
+
on<TName extends keyof TEvents & string>(name: TName, listener: (data: TEvents[TName]) => void | Promise<void>): UnsubscribeFunction;
|
|
25
|
+
/**
|
|
26
|
+
* Emit an event immediately (not inside any transaction).
|
|
27
|
+
*/
|
|
28
|
+
emit<TName extends keyof TEvents & string>(name: TName, data: TEvents[TName]): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Begin a new transaction context for event buffering.
|
|
31
|
+
* Events emitted via `emitBuffered` with this transaction ID
|
|
32
|
+
* will be held until `commit` or `rollback` is called.
|
|
33
|
+
*/
|
|
34
|
+
beginTransaction(transactionID: string): void;
|
|
35
|
+
/**
|
|
36
|
+
* Emit an event inside a transaction. The event is buffered
|
|
37
|
+
* and will only be delivered to listeners upon commit.
|
|
38
|
+
*/
|
|
39
|
+
emitBuffered<TName extends keyof TEvents & string>(transactionID: string, name: TName, data: TEvents[TName]): void;
|
|
40
|
+
/**
|
|
41
|
+
* Commit a transaction: release all buffered events to listeners
|
|
42
|
+
* in the order they were emitted.
|
|
43
|
+
*
|
|
44
|
+
* Listener errors do not prevent remaining buffered events from firing.
|
|
45
|
+
* Since `@enkaku/event` uses `Promise.allSettled` internally, all listeners
|
|
46
|
+
* for a given event run even if some throw. Errors from individual event
|
|
47
|
+
* emissions are caught so the full buffer is always drained.
|
|
48
|
+
*/
|
|
49
|
+
commit(transactionID: string): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Rollback a transaction: discard all buffered events.
|
|
52
|
+
*/
|
|
53
|
+
rollback(transactionID: string): void;
|
|
54
|
+
}
|
|
55
|
+
export {};
|
package/lib/events.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{EventEmitter as t}from"@enkaku/event";export class EngineEventBus{#t;#r;#n;constructor(r){this.#t=new t,this.#r=r?.logger,this.#n=new Map}on(t,r){return this.#t.on(t,r)}async emit(t,r){await this.#t.emit(t,r)}beginTransaction(t){if(this.#n.has(t))throw Error(`Transaction already exists: ${t}`);this.#n.set(t,[])}emitBuffered(t,r,n){let e=this.#n.get(t);if(null==e)throw Error(`Transaction not found: ${t}`);e.push({name:r,data:n})}async commit(t){let r=this.#n.get(t);if(null==r)throw Error(`Transaction not found: ${t}`);for(let n of(this.#n.delete(t),r))try{await this.#t.emit(n.name,n.data)}catch(t){this.#r?.warn("event listener error during commit for {event}: {error}",{event:n.name,error:String(t)})}}rollback(t){if(!this.#n.has(t))throw Error(`Transaction not found: ${t}`);this.#n.delete(t)}}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ExecuteGraphParams, ExecuteGraphResult, GraphsProvider } from '@kubun/protocol';
|
|
2
|
+
export type { GraphQLOperationParams, GraphQLSource, GraphQLSourceParams, GraphQLSourceProvider, GraphsProvider, } from '@kubun/protocol';
|
|
3
|
+
import type { GetAPI } from './registry.js';
|
|
4
|
+
/**
|
|
5
|
+
* Engine-specific graph params extending the protocol-level params with viewerDID.
|
|
6
|
+
* viewerDID is resolved server-side (not sent over the wire), so it belongs
|
|
7
|
+
* on the engine type rather than the protocol schema.
|
|
8
|
+
*/
|
|
9
|
+
export type EngineGraphParams = ExecuteGraphParams & {
|
|
10
|
+
viewerDID?: string;
|
|
11
|
+
};
|
|
12
|
+
export type Engine = Omit<GraphsProvider, 'queryGraph' | 'mutateGraph' | 'subscribeToGraph'> & {
|
|
13
|
+
queryGraph<Data extends Record<string, unknown> = Record<string, unknown>>(params: EngineGraphParams): Promise<ExecuteGraphResult<Data>>;
|
|
14
|
+
mutateGraph<Data extends Record<string, unknown> = Record<string, unknown>>(params: EngineGraphParams): Promise<ExecuteGraphResult<Data>>;
|
|
15
|
+
subscribeToGraph<Data extends Record<string, unknown> = Record<string, unknown>>(params: EngineGraphParams): Promise<AsyncGenerator<ExecuteGraphResult<Data>> | ExecuteGraphResult<Data>>;
|
|
16
|
+
getAPI: GetAPI;
|
|
17
|
+
ready: Promise<void>;
|
|
18
|
+
};
|
package/lib/executor.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{};
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type { AccessChecker, AccessLevel, AccessPermissions, AccessRule, DefaultAccessLevel, } from './access-control.js';
|
|
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';
|
|
4
|
+
export { KubunEngine } from './engine.js';
|
|
5
|
+
export type { EngineEvents } from './engine-events.js';
|
|
6
|
+
export { EngineEventBus } from './events.js';
|
|
7
|
+
export type { Engine, EngineGraphParams, GraphQLOperationParams, GraphQLSource, GraphQLSourceParams, GraphQLSourceProvider, GraphsProvider, } from './executor.js';
|
|
8
|
+
export { computeMutationHash } from './mutation-hash.js';
|
|
9
|
+
export type { GraphInternals, KubunPlugin, PluginFactoryParams, SchemaExtension } from './plugin.js';
|
|
10
|
+
export type { AsyncPolicyCheck, PolicyGateMap, PolicyIssue, PolicyPathSegment, PolicyResult, SyncPolicyCheck, } from './policies.js';
|
|
11
|
+
export { runPolicies } from './policies.js';
|
|
12
|
+
export type { GetAPI, Registry } from './registry.js';
|
|
13
|
+
export { createRegistry } from './registry.js';
|
package/lib/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{createAccessChecker,parseDocumentAccessPermissions,validateDIDs}from"./access-control.js";export{KubunEngine}from"./engine.js";export{EngineEventBus}from"./events.js";export{computeMutationHash}from"./mutation-hash.js";export{runPolicies}from"./policies.js";export{createRegistry}from"./registry.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function computeMutationHash(jwt: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{blake3 as e}from"@noble/hashes/blake3.js";import{bytesToHex as o}from"@noble/hashes/utils.js";let t=new TextEncoder;export function computeMutationHash(n){return o(e(t.encode(n)))}
|
package/lib/plugin.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Runtime } from '@enkaku/runtime';
|
|
2
|
+
import type { Identity } from '@enkaku/token';
|
|
3
|
+
import type { KubunDB } from '@kubun/db';
|
|
4
|
+
import type { ExtensionResolvers } from '@kubun/graphql';
|
|
5
|
+
import type { HLC } from '@kubun/hlc';
|
|
6
|
+
import type { Logger } from '@kubun/logger';
|
|
7
|
+
import type { ExecutionResult } from 'graphql';
|
|
8
|
+
import type { ApplyVerifiedMutationParams, ApplyVerifiedMutationResult, ApplyVerifiedMutationsParams, ApplyVerifiedMutationsResult, ContextFactory, ExecuteParams, PluginMigrations } from './engine.js';
|
|
9
|
+
import type { EngineEventBus } from './events.js';
|
|
10
|
+
import type { Engine } from './executor.js';
|
|
11
|
+
import type { PolicyGateMap } from './policies.js';
|
|
12
|
+
export type SchemaExtension = {
|
|
13
|
+
sdl: string;
|
|
14
|
+
resolvers: ExtensionResolvers;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Low-level graph operations available only to plugins.
|
|
18
|
+
* These bypass the public Engine API to allow plugins (RPC, sync, etc.)
|
|
19
|
+
* to execute GraphQL and apply pre-verified mutations directly.
|
|
20
|
+
*/
|
|
21
|
+
export type GraphInternals = {
|
|
22
|
+
execute<Data extends Record<string, unknown> = Record<string, unknown>>(params: ExecuteParams): Promise<ExecutionResult<Data>>;
|
|
23
|
+
subscribe<Data extends Record<string, unknown> = Record<string, unknown>>(params: ExecuteParams): Promise<AsyncGenerator<ExecutionResult<Data>> | ExecutionResult<Data>>;
|
|
24
|
+
applyVerifiedMutation(params: ApplyVerifiedMutationParams): Promise<ApplyVerifiedMutationResult>;
|
|
25
|
+
applyVerifiedMutations(params: ApplyVerifiedMutationsParams): Promise<ApplyVerifiedMutationsResult>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Parameters provided by the engine to plugin factories.
|
|
29
|
+
* Shared resources flow from the engine — plugins must use these
|
|
30
|
+
* rather than creating their own instances.
|
|
31
|
+
*/
|
|
32
|
+
export type PluginFactoryParams = {
|
|
33
|
+
engine: Engine;
|
|
34
|
+
graph: GraphInternals;
|
|
35
|
+
db: KubunDB;
|
|
36
|
+
runtime: Runtime;
|
|
37
|
+
identity: Identity;
|
|
38
|
+
eventBus: EngineEventBus;
|
|
39
|
+
hlc: HLC;
|
|
40
|
+
getLogger: (name: string) => Logger;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Plugin descriptor returned by a plugin factory.
|
|
44
|
+
*
|
|
45
|
+
* Plugins expose APIs via the `api` field. Other plugins access them
|
|
46
|
+
* via `engine.getAPI(name)`. Lifecycle coordination uses lazy promises
|
|
47
|
+
* on plugin APIs (e.g. `httpAPI.listening`) rather than a central registry.
|
|
48
|
+
*/
|
|
49
|
+
export type KubunPlugin = {
|
|
50
|
+
name: string;
|
|
51
|
+
migrations?: PluginMigrations;
|
|
52
|
+
policies?: PolicyGateMap;
|
|
53
|
+
schemaExtension?: (config: Record<string, unknown>) => SchemaExtension;
|
|
54
|
+
events?: Record<string, string>;
|
|
55
|
+
/** Plugin's public API, available to other plugins via engine.getAPI(name) */
|
|
56
|
+
api?: unknown;
|
|
57
|
+
createContextFactory?: () => ContextFactory;
|
|
58
|
+
dispose?: () => Promise<void> | void;
|
|
59
|
+
};
|
package/lib/plugin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy gate module for the Kubun engine.
|
|
3
|
+
*
|
|
4
|
+
* Policies gate engine operations with sync-before-async ordering.
|
|
5
|
+
* Each gate has optional sync and async check arrays. Sync checks run first
|
|
6
|
+
* (fast, in-memory). If any sync check fails, async checks are skipped entirely.
|
|
7
|
+
*
|
|
8
|
+
* Result types follow the Standard Schema output specification:
|
|
9
|
+
* - Success: `{ value }` — the context passes through
|
|
10
|
+
* - Failure: `{ issues }` — an array of issues with at minimum a `message` field
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* A path segment in a policy issue, matching StandardSchemaV1.PathSegment.
|
|
14
|
+
*/
|
|
15
|
+
export type PolicyPathSegment = {
|
|
16
|
+
readonly key: PropertyKey;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* A policy issue, matching StandardSchemaV1.Issue.
|
|
20
|
+
*/
|
|
21
|
+
export type PolicyIssue = {
|
|
22
|
+
readonly message: string;
|
|
23
|
+
readonly path?: ReadonlyArray<PropertyKey | PolicyPathSegment> | undefined;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Successful policy check result.
|
|
27
|
+
*/
|
|
28
|
+
export type PolicySuccessResult<TContext = unknown> = {
|
|
29
|
+
readonly value: TContext;
|
|
30
|
+
readonly issues?: undefined;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Failed policy check result.
|
|
34
|
+
*/
|
|
35
|
+
export type PolicyFailureResult = {
|
|
36
|
+
readonly issues: ReadonlyArray<PolicyIssue>;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* The result of a policy check — either success or failure.
|
|
40
|
+
*/
|
|
41
|
+
export type PolicyResult<TContext = unknown> = PolicySuccessResult<TContext> | PolicyFailureResult;
|
|
42
|
+
/**
|
|
43
|
+
* A synchronous policy check function.
|
|
44
|
+
* Returns a Standard Schema result immediately (in-memory, fast).
|
|
45
|
+
*/
|
|
46
|
+
export type SyncPolicyCheck<TContext = unknown> = (context: TContext) => PolicyResult<TContext>;
|
|
47
|
+
/**
|
|
48
|
+
* An asynchronous policy check function.
|
|
49
|
+
* Returns a Promise of a Standard Schema result (may involve DB lookups, etc.).
|
|
50
|
+
*/
|
|
51
|
+
export type AsyncPolicyCheck<TContext = unknown> = (context: TContext) => Promise<PolicyResult<TContext>>;
|
|
52
|
+
/**
|
|
53
|
+
* A single policy gate with optional sync and async check arrays.
|
|
54
|
+
*/
|
|
55
|
+
export type PolicyGate<TContext = unknown> = {
|
|
56
|
+
sync?: Array<SyncPolicyCheck<TContext>>;
|
|
57
|
+
async?: Array<AsyncPolicyCheck<TContext>>;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Map of gate type names to their policy gates.
|
|
61
|
+
*/
|
|
62
|
+
export type PolicyGateMap<TContext = unknown> = Record<string, PolicyGate<TContext>>;
|
|
63
|
+
/**
|
|
64
|
+
* Run all policy checks for a given gate type.
|
|
65
|
+
*
|
|
66
|
+
* Execution order:
|
|
67
|
+
* 1. Run ALL sync checks — fail fast if any return issues
|
|
68
|
+
* 2. Run ALL async checks — fail fast if any return issues
|
|
69
|
+
* 3. Return success with the original context
|
|
70
|
+
*
|
|
71
|
+
* If the gate type is not present in the map, the operation is allowed by default.
|
|
72
|
+
*/
|
|
73
|
+
export declare function runPolicies<TContext>(gates: PolicyGateMap<TContext>, gateType: string, context: TContext): Promise<PolicyResult<TContext>>;
|
package/lib/policies.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e){return"issues"in e&&null!=e.issues&&e.issues.length>0}export async function runPolicies(n,t,u){let r=n[t];if(null==r)return{value:u};for(let n of r.sync??[]){let t=n(u);if(e(t))return t}for(let n of r.async??[]){let t=await n(u);if(e(t))return t}return{value:u}}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type GetAPI = <T extends Record<string, unknown> = Record<string, unknown>>(name: string) => Promise<T>;
|
|
2
|
+
export type Registry = {
|
|
3
|
+
registerPlugin(name: string, api: unknown): void;
|
|
4
|
+
closeGate(): void;
|
|
5
|
+
getAPI: GetAPI;
|
|
6
|
+
};
|
|
7
|
+
export declare function createRegistry(): Registry;
|
package/lib/registry.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{defer as e}from"@enkaku/async";export function createRegistry(){let r=e(),t={};return{registerPlugin:function(e,r){t[e]=r},closeGate:function(){r.resolve()},getAPI:function(e){return r.promise.then(()=>{let r=t[e];if(null==r)throw Error(`API not registered: ${e}`);return r})}}}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kubun/engine",
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"license": "see LICENSE.md",
|
|
5
|
+
"keywords": [],
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "lib/index.js",
|
|
8
|
+
"types": "lib/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./lib/index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"lib/*",
|
|
14
|
+
"LICENSE.md"
|
|
15
|
+
],
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@enkaku/async": "^0.14.0",
|
|
19
|
+
"@enkaku/capability": "^0.14.0",
|
|
20
|
+
"@enkaku/event": "^0.14.1",
|
|
21
|
+
"@enkaku/runtime": "^0.14.0",
|
|
22
|
+
"@enkaku/schema": "^0.14.0",
|
|
23
|
+
"@enkaku/token": "0.14.1",
|
|
24
|
+
"@noble/hashes": "^2.2.0",
|
|
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",
|
|
32
|
+
"@kubun/logger": "^0.8.0",
|
|
33
|
+
"@kubun/hlc": "^0.8.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@kubun/id": "^0.8.0",
|
|
37
|
+
"@kubun/test-utils": "^0.8.0"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build:clean": "del lib",
|
|
41
|
+
"build:js": "swc src -d ./lib --config-file ../../swc.json --strip-leading-paths",
|
|
42
|
+
"build:types": "tsc --emitDeclarationOnly --skipLibCheck",
|
|
43
|
+
"build:types:ci": "tsc --emitDeclarationOnly --declarationMap false",
|
|
44
|
+
"build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
|
|
45
|
+
"test:types": "tsc --noEmit -p tsconfig.test.json",
|
|
46
|
+
"test:unit": "vitest run",
|
|
47
|
+
"test": "pnpm run test:types && pnpm run test:unit"
|
|
48
|
+
}
|
|
49
|
+
}
|