@gp2f/server 0.1.1 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -0
- package/gp2f_node.darwin-arm64.node +0 -0
- package/index.d.ts +20 -267
- package/package.json +3 -3
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# @gp2f/server
|
|
2
|
+
|
|
3
|
+
Native Node.js bindings for the GP2F policy engine – powered by napi-rs.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
This package provides native bindings to use the core functionality of the GP2F policy engine directly from Node.js applications. It allows for high-performance rule evaluation and state processing.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
```bash
|
|
10
|
+
npm install @gp2f/server
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### 1. Evaluating a Policy (Stateless)
|
|
16
|
+
You can evaluate a state document against a GP2F AST policy directly:
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { evaluate, evaluateWithTrace, AstNode } from '@gp2f/server';
|
|
20
|
+
|
|
21
|
+
const policy: AstNode = {
|
|
22
|
+
kind: 'And',
|
|
23
|
+
children: [
|
|
24
|
+
{ kind: 'Field', path: '/role', value: 'admin' },
|
|
25
|
+
{ kind: 'Exists', path: '/session/token' }
|
|
26
|
+
]
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const state = {
|
|
30
|
+
role: 'admin',
|
|
31
|
+
session: { token: 'abc-123' }
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Simple boolean evaluation
|
|
35
|
+
const isAllowed = evaluate(policy, state);
|
|
36
|
+
console.log('Allowed:', isAllowed); // true
|
|
37
|
+
|
|
38
|
+
// Evaluation with a step-by-step trace
|
|
39
|
+
const { result, trace } = evaluateWithTrace(policy, state);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. Embedding the GP2F Server & Workflows
|
|
43
|
+
You can create a complete reconciliation server and define workflows in Node.js:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { GP2FServer, Workflow } from '@gp2f/server';
|
|
47
|
+
|
|
48
|
+
async function main() {
|
|
49
|
+
const server = new GP2FServer({ port: 3000 });
|
|
50
|
+
|
|
51
|
+
// Define a new workflow
|
|
52
|
+
const wf = new Workflow('document-approval');
|
|
53
|
+
|
|
54
|
+
// Register an activity with a policy and a callback
|
|
55
|
+
wf.addActivity(
|
|
56
|
+
'review-step',
|
|
57
|
+
{ policy: { kind: 'LiteralTrue' } },
|
|
58
|
+
async (ctx) => {
|
|
59
|
+
console.log(`Executing ${ctx.activityName} for instance ${ctx.instanceId}`);
|
|
60
|
+
console.log('State:', JSON.parse(ctx.stateJson));
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// Register workflow to the server
|
|
65
|
+
server.register(wf);
|
|
66
|
+
|
|
67
|
+
// Start handling HTTP requests
|
|
68
|
+
await server.start();
|
|
69
|
+
console.log(`GP2F Server listening on port ${server.port}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
main().catch(console.error);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Development
|
|
76
|
+
This package uses `napi-rs` to build the Rust bindings.
|
|
77
|
+
|
|
78
|
+
- Build cross-platform artifacts: `npm run artifacts`
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
MIT
|
|
Binary file
|
package/index.d.ts
CHANGED
|
@@ -1,276 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
// Auto-resolve library typings for GP2F
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export type NodeKind =
|
|
9
|
-
| 'LiteralTrue'
|
|
10
|
-
| 'LiteralFalse'
|
|
11
|
-
| 'And'
|
|
12
|
-
| 'Or'
|
|
13
|
-
| 'Not'
|
|
14
|
-
| 'Eq'
|
|
15
|
-
| 'Neq'
|
|
16
|
-
| 'Gt'
|
|
17
|
-
| 'Gte'
|
|
18
|
-
| 'Lt'
|
|
19
|
-
| 'Lte'
|
|
20
|
-
| 'In'
|
|
21
|
-
| 'Contains'
|
|
22
|
-
| 'Exists'
|
|
23
|
-
| 'Field'
|
|
24
|
-
| 'Call'
|
|
25
|
-
| 'VibeCheck'
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* A node in the GP2F policy AST.
|
|
29
|
-
*
|
|
30
|
-
* Build a tree of these to express the policy that governs whether a given
|
|
31
|
-
* activity is permitted.
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```typescript
|
|
35
|
-
* const policy: AstNode = {
|
|
36
|
-
* kind: 'And',
|
|
37
|
-
* children: [
|
|
38
|
-
* { kind: 'Field', path: '/user/role', value: 'admin' },
|
|
39
|
-
* { kind: 'Exists', path: '/session/token' },
|
|
40
|
-
* ],
|
|
41
|
-
* };
|
|
42
|
-
* ```
|
|
43
|
-
*/
|
|
44
|
-
export interface AstNode {
|
|
45
|
-
/** The operation this node performs (required). */
|
|
46
|
-
kind: NodeKind
|
|
47
|
-
/** Child nodes for composite operators (And, Or, Not, comparison, …). */
|
|
48
|
-
children?: AstNode[]
|
|
49
|
-
/** JSON-pointer path used by `Field` and `Exists` nodes. */
|
|
50
|
-
path?: string
|
|
51
|
-
/** String-encoded scalar value for leaf nodes, e.g. `"admin"` or `"42"`. */
|
|
52
|
-
value?: string
|
|
53
|
-
/** Name of the external function – used only by `Call` nodes. */
|
|
54
|
-
callName?: string
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Result of a policy evaluation including the decision trace.
|
|
59
|
-
*/
|
|
60
|
-
export interface EvalResult {
|
|
61
|
-
/** `true` when the policy permits the operation. */
|
|
62
|
-
result: boolean
|
|
63
|
-
/** Human-readable trace of each evaluation step (useful for debugging). */
|
|
64
|
-
trace: string[]
|
|
3
|
+
export interface PolicyNode {
|
|
4
|
+
kind: string;
|
|
5
|
+
path?: string;
|
|
6
|
+
value?: string;
|
|
7
|
+
children?: PolicyNode[];
|
|
65
8
|
}
|
|
66
9
|
|
|
67
|
-
/**
|
|
68
|
-
* Evaluate a policy AST against a JSON state object.
|
|
69
|
-
*
|
|
70
|
-
* @param policy - Root node of the policy AST.
|
|
71
|
-
* @param state - Arbitrary JSON object representing the current state.
|
|
72
|
-
* @returns `true` when the policy permits the operation, `false` otherwise.
|
|
73
|
-
*
|
|
74
|
-
* @example
|
|
75
|
-
* ```typescript
|
|
76
|
-
* import { evaluate } from '@gp2f/server';
|
|
77
|
-
*
|
|
78
|
-
* const allowed = evaluate(
|
|
79
|
-
* { kind: 'Field', path: '/role', value: 'admin' },
|
|
80
|
-
* { role: 'admin' }
|
|
81
|
-
* );
|
|
82
|
-
* // => true
|
|
83
|
-
* ```
|
|
84
|
-
*/
|
|
85
|
-
export function evaluate(policy: AstNode, state: unknown): boolean
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Evaluate a policy AST and return the full evaluation trace.
|
|
89
|
-
*
|
|
90
|
-
* @param policy - Root node of the policy AST.
|
|
91
|
-
* @param state - Arbitrary JSON object representing the current state.
|
|
92
|
-
* @returns An {@link EvalResult} with the boolean decision and a trace array.
|
|
93
|
-
*/
|
|
94
|
-
export function evaluateWithTrace(policy: AstNode, state: unknown): EvalResult
|
|
95
|
-
|
|
96
|
-
// ── Workflow ────────────────────────────────────────────────────────────────
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Context object passed to every `onExecute` callback.
|
|
100
|
-
*/
|
|
101
|
-
export interface ExecutionContext {
|
|
102
|
-
/** Unique workflow execution identifier. */
|
|
103
|
-
instanceId: string
|
|
104
|
-
/** Tenant/organisation this execution belongs to. */
|
|
105
|
-
tenantId: string
|
|
106
|
-
/** Name of the activity currently executing. */
|
|
107
|
-
activityName: string
|
|
108
|
-
/**
|
|
109
|
-
* The JSON-encoded state document evaluated by the policy engine.
|
|
110
|
-
*
|
|
111
|
-
* Parse with `JSON.parse(ctx.stateJson)` to get the object.
|
|
112
|
-
*/
|
|
113
|
-
stateJson: string
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Configuration object for a single workflow activity.
|
|
118
|
-
*/
|
|
119
10
|
export interface ActivityConfig {
|
|
120
|
-
|
|
121
|
-
policy: AstNode
|
|
122
|
-
/**
|
|
123
|
-
* Optional name of a registered compensation handler to undo this activity
|
|
124
|
-
* if a later step fails (Saga pattern).
|
|
125
|
-
*/
|
|
126
|
-
compensationRef?: string
|
|
127
|
-
/**
|
|
128
|
-
* When `true`, this activity runs as a Local Activity (no Temporal
|
|
129
|
-
* persistence round-trip). Use for short, idempotent operations.
|
|
130
|
-
*/
|
|
131
|
-
isLocal?: boolean
|
|
11
|
+
policy: PolicyNode;
|
|
132
12
|
}
|
|
133
13
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
* {@link GP2FServer.register}.
|
|
139
|
-
*
|
|
140
|
-
* @example
|
|
141
|
-
* ```typescript
|
|
142
|
-
* import { Workflow } from '@gp2f/server';
|
|
143
|
-
*
|
|
144
|
-
* const wf = new Workflow('document-approval');
|
|
145
|
-
* wf.addActivity(
|
|
146
|
-
* 'review',
|
|
147
|
-
* { policy: { kind: 'LiteralTrue' } },
|
|
148
|
-
* async (ctx) => { console.log('executing', ctx.activityName); }
|
|
149
|
-
* );
|
|
150
|
-
* ```
|
|
151
|
-
*/
|
|
152
|
-
export class Workflow {
|
|
153
|
-
/**
|
|
154
|
-
* Create a new workflow with the given identifier.
|
|
155
|
-
* @param workflowId - Stable identifier for this workflow.
|
|
156
|
-
*/
|
|
157
|
-
constructor(workflowId: string)
|
|
158
|
-
|
|
159
|
-
/** The workflow identifier. */
|
|
160
|
-
readonly id: string
|
|
161
|
-
|
|
162
|
-
/** The number of registered activities. */
|
|
163
|
-
readonly activityCount: number
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Add an activity to this workflow.
|
|
167
|
-
*
|
|
168
|
-
* Activities are executed in the order they are added. Each activity has a
|
|
169
|
-
* policy AST that determines whether the operation is permitted.
|
|
170
|
-
*
|
|
171
|
-
* The optional `onExecute` callback is invoked when the activity is
|
|
172
|
-
* accepted. It receives an {@link ExecutionContext} and may be async; the
|
|
173
|
-
* Rust runtime invokes it on the Node.js event loop via a threadsafe
|
|
174
|
-
* function handle.
|
|
175
|
-
*
|
|
176
|
-
* @param name - Unique name for this activity within the workflow.
|
|
177
|
-
* @param config - Activity configuration including the policy AST.
|
|
178
|
-
* @param onExecute - Optional async callback invoked when the activity runs.
|
|
179
|
-
* @returns The workflow identifier (for informational purposes).
|
|
180
|
-
*/
|
|
181
|
-
addActivity(
|
|
182
|
-
name: string,
|
|
183
|
-
config: ActivityConfig,
|
|
184
|
-
onExecute?: (ctx: ExecutionContext) => void | Promise<unknown>
|
|
185
|
-
): string
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Evaluate the workflow against a state document without side-effects.
|
|
189
|
-
*
|
|
190
|
-
* @param state - JSON state document to evaluate against.
|
|
191
|
-
* @returns `true` when *every* activity policy is satisfied.
|
|
192
|
-
*/
|
|
193
|
-
dryRun(state: unknown): boolean
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// ── Server ──────────────────────────────────────────────────────────────────
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Configuration object for {@link GP2FServer}.
|
|
200
|
-
*/
|
|
201
|
-
export interface ServerConfig {
|
|
202
|
-
/** TCP port the server should listen on. Defaults to `3000`. */
|
|
203
|
-
port?: number
|
|
204
|
-
/** Hostname / bind address. Defaults to `"127.0.0.1"`. */
|
|
205
|
-
host?: string
|
|
14
|
+
export class JsGp2FServer {
|
|
15
|
+
constructor(config: { port: number });
|
|
16
|
+
register(workflow: JsWorkflow): void;
|
|
17
|
+
start(): Promise<void>;
|
|
206
18
|
}
|
|
207
19
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
* |--------|-----------------------|------------------------------------------|
|
|
218
|
-
* | GET | `/health` | Health-check, returns `"ok"`. |
|
|
219
|
-
* | POST | `/workflow/run` | Execute the next activity of a workflow. |
|
|
220
|
-
* | POST | `/workflow/dry-run` | Evaluate policies without side-effects. |
|
|
221
|
-
*
|
|
222
|
-
* @example
|
|
223
|
-
* ```typescript
|
|
224
|
-
* import { GP2FServer, Workflow } from '@gp2f/server';
|
|
225
|
-
*
|
|
226
|
-
* const server = new GP2FServer({ port: 3000 });
|
|
227
|
-
*
|
|
228
|
-
* const wf = new Workflow('my-workflow');
|
|
229
|
-
* wf.addActivity('step1', { policy: { kind: 'LiteralTrue' } });
|
|
230
|
-
*
|
|
231
|
-
* server.register(wf);
|
|
232
|
-
* await server.start();
|
|
233
|
-
* // ... later:
|
|
234
|
-
* await server.stop();
|
|
235
|
-
* ```
|
|
236
|
-
*/
|
|
237
|
-
export class GP2FServer {
|
|
238
|
-
/**
|
|
239
|
-
* Create a new server instance.
|
|
240
|
-
*
|
|
241
|
-
* The server is not started until {@link GP2FServer.start} is called.
|
|
242
|
-
*
|
|
243
|
-
* @param config - Optional server configuration.
|
|
244
|
-
*/
|
|
245
|
-
constructor(config?: ServerConfig)
|
|
246
|
-
|
|
247
|
-
/** The port the server is (or will be) listening on. */
|
|
248
|
-
readonly port: number
|
|
249
|
-
|
|
250
|
-
/** `true` while the server is running. */
|
|
251
|
-
readonly isRunning: boolean
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Register a workflow definition with the server.
|
|
255
|
-
*
|
|
256
|
-
* Workflows can be registered at any time, even while the server is running.
|
|
257
|
-
*
|
|
258
|
-
* @param workflow - The workflow to register.
|
|
259
|
-
*/
|
|
260
|
-
register(workflow: Workflow): void
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Start the HTTP server.
|
|
264
|
-
*
|
|
265
|
-
* Resolves once the TCP listener is bound and the server is ready to accept
|
|
266
|
-
* connections.
|
|
267
|
-
*/
|
|
268
|
-
start(): Promise<void>
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Stop the server gracefully.
|
|
272
|
-
*
|
|
273
|
-
* Sends a shutdown signal and waits for in-flight requests to drain.
|
|
274
|
-
*/
|
|
275
|
-
stop(): Promise<void>
|
|
20
|
+
export class JsWorkflow {
|
|
21
|
+
constructor(id: string);
|
|
22
|
+
addActivity(
|
|
23
|
+
name: string,
|
|
24
|
+
config: ActivityConfig,
|
|
25
|
+
handler: (ctx: any) => Promise<void>
|
|
26
|
+
): void;
|
|
27
|
+
activityCount(): number;
|
|
28
|
+
id(): string;
|
|
276
29
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gp2f/server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Native Node.js bindings for the GP2F policy engine – powered by napi-rs",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"gp2f_node.*.node"
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
|
-
"build": "napi build --platform --release",
|
|
14
|
-
"build:debug": "napi build --platform",
|
|
13
|
+
"build": "napi build --platform --release --dts .napi-empty.d.ts",
|
|
14
|
+
"build:debug": "napi build --platform --dts .napi-empty.d.ts",
|
|
15
15
|
"artifacts": "napi artifacts",
|
|
16
16
|
"test": "jest --passWithNoTests"
|
|
17
17
|
},
|