@gp2f/server 0.1.6 → 0.1.8

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 CHANGED
@@ -72,6 +72,34 @@ async function main() {
72
72
  main().catch(console.error);
73
73
  ```
74
74
 
75
+ ### 3. Fluent Policy Builder
76
+
77
+ Build policy ASTs with a chainable API instead of raw JSON objects:
78
+
79
+ ```typescript
80
+ import { p } from '@gp2f/server';
81
+
82
+ // Field equality
83
+ const policy = p.field('/role').eq('admin');
84
+
85
+ // Logical AND
86
+ const policy = p.and(
87
+ p.field('/role').eq('clinician'),
88
+ p.exists('/patient_id'),
89
+ );
90
+
91
+ // Role allow-list
92
+ const policy = p.field('/role').in(['admin', 'editor', 'reviewer']);
93
+
94
+ // Numeric comparison
95
+ const policy = p.field('/score').gte(80);
96
+
97
+ // Vibe Engine gate
98
+ const policy = p.vibe('frustrated').withConfidence(0.8).build();
99
+ ```
100
+
101
+ The builder output is a plain `AstNode` that works with `evaluate()`, `evaluateWithTrace()`, `addActivity()`, and anywhere else a policy AST is accepted.
102
+
75
103
  ## Development
76
104
  This package uses `napi-rs` to build the Rust bindings.
77
105
 
package/index.d.ts CHANGED
@@ -1,30 +1,200 @@
1
- // Auto-resolve library typings for GP2F
1
+ /* eslint-disable */
2
+ /* tslint:disable */
3
+ /* auto-generated – mirrors gp2f-node/src/*.rs napi exports */
2
4
 
3
- export interface PolicyNode {
4
- kind: string;
5
- path?: string;
6
- value?: string;
7
- children?: PolicyNode[];
5
+ // ── Node kinds ────────────────────────────────────────────────────────────────
6
+
7
+ export type NodeKind =
8
+ | 'LiteralTrue'
9
+ | 'LiteralFalse'
10
+ | 'And'
11
+ | 'Or'
12
+ | 'Not'
13
+ | 'Eq'
14
+ | 'Neq'
15
+ | 'Gt'
16
+ | 'Gte'
17
+ | 'Lt'
18
+ | 'Lte'
19
+ | 'In'
20
+ | 'Contains'
21
+ | 'Exists'
22
+ | 'Field'
23
+ | 'Call'
24
+ | 'VibeCheck'
25
+
26
+ // ── Core AST ──────────────────────────────────────────────────────────────────
27
+
28
+ /** A node in the GP2F policy AST. */
29
+ export interface AstNode {
30
+ /** The operation this node performs (required). */
31
+ kind: string
32
+ /** Child nodes for composite operators (AND, OR, NOT, comparison, …). */
33
+ children?: AstNode[]
34
+ /** JSON-pointer path used by `FIELD` and `EXISTS` nodes (e.g. `/user/role`). */
35
+ path?: string
36
+ /** String-encoded scalar value for leaf nodes (e.g. `"admin"`, `"42"`). */
37
+ value?: string
38
+ /** Name of the external function – used only by `CALL` nodes. */
39
+ callName?: string
8
40
  }
9
41
 
42
+ // ── Activity & server configuration ──────────────────────────────────────────
43
+
44
+ /** Configuration object for a single workflow activity. */
10
45
  export interface ActivityConfig {
11
- policy: PolicyNode;
46
+ /** Policy AST that governs whether this activity is permitted. */
47
+ policy: AstNode | PolicyInput
48
+ /** Optional name of a registered compensation handler. */
49
+ compensationRef?: string
50
+ /** When `true`, this activity runs as a Local Activity. */
51
+ isLocal?: boolean
52
+ }
53
+
54
+ /** Server startup configuration. */
55
+ export interface ServerConfig {
56
+ /** TCP port to listen on. Defaults to 3000. */
57
+ port?: number
58
+ /** Bind address. Defaults to `"0.0.0.0"`. */
59
+ host?: string
60
+ }
61
+
62
+ /** Context passed to every `onExecute` callback. */
63
+ export interface ExecutionContext {
64
+ /** Unique workflow execution identifier. */
65
+ instanceId: string
66
+ /** Tenant/organisation this execution belongs to. */
67
+ tenantId: string
68
+ /** Name of the activity currently executing. */
69
+ activityName: string
70
+ /** The JSON-encoded state document. Use `JSON.parse(ctx.stateJson)`. */
71
+ stateJson: string
12
72
  }
13
73
 
14
- export class JsGp2FServer {
15
- constructor(config: { port?: number; host?: string });
16
- register(workflow: JsWorkflow): void;
17
- start(): Promise<void>;
74
+ /** Result of a policy evaluation including the decision trace. */
75
+ export interface EvalResult {
76
+ /** `true` when the policy permits the operation. */
77
+ result: boolean
78
+ /** Human-readable trace of each evaluation step (for debugging). */
79
+ trace: string[]
18
80
  }
19
81
 
82
+ // ── Native functions ──────────────────────────────────────────────────────────
83
+
84
+ /**
85
+ * Evaluate a policy AST against a JSON state object.
86
+ *
87
+ * Returns `true` when the policy permits the operation.
88
+ */
89
+ export function evaluate(policy: AstNode, state: object): boolean
90
+
91
+ /**
92
+ * Evaluate a policy AST and return the full evaluation trace.
93
+ *
94
+ * Useful for debugging policies.
95
+ */
96
+ export function evaluateWithTrace(policy: AstNode, state: object): EvalResult
20
97
 
21
- export class JsWorkflow {
22
- constructor(id: string);
98
+ // ── Workflow class ────────────────────────────────────────────────────────────
99
+
100
+ export class Workflow {
101
+ constructor(workflowId: string)
102
+
103
+ /** Add an activity to this workflow. */
23
104
  addActivity(
24
105
  name: string,
25
106
  config: ActivityConfig,
26
- handler: (ctx: any) => Promise<void>
27
- ): void;
28
- activityCount(): number;
29
- id(): string;
107
+ onExecute?: (ctx: ExecutionContext) => Promise<void> | void,
108
+ ): string
109
+
110
+ /** The workflow identifier. */
111
+ readonly id: string
112
+
113
+ /** Number of registered activities. */
114
+ readonly activityCount: number
115
+
116
+ /** Evaluate all activity policies against `state` (no side-effects). */
117
+ dryRun(state: object): boolean
118
+ }
119
+
120
+ // ── GP2FServer class ──────────────────────────────────────────────────────────
121
+
122
+ export class GP2FServer {
123
+ constructor(config?: ServerConfig)
124
+
125
+ /** Register a workflow with this server. */
126
+ register(workflow: Workflow): void
127
+
128
+ /** Start the HTTP server. */
129
+ start(): Promise<void>
130
+
131
+ /** Stop the HTTP server. */
132
+ stop(): Promise<void>
133
+
134
+ /** The configured TCP port. */
135
+ readonly port: number
136
+
137
+ /** `true` when the server is currently accepting connections. */
138
+ readonly isRunning: boolean
139
+ }
140
+
141
+ // ── Fluent Policy Builder ─────────────────────────────────────────────────────
142
+
143
+ /** Shared interface for all builder objects that can produce an AstNode. */
144
+ export interface Builder {
145
+ build(): AstNode
146
+ toJSON(): AstNode
147
+ }
148
+
149
+ /** A policy value that is either a raw AstNode or a Builder. */
150
+ export type PolicyInput = AstNode | Builder
151
+
152
+ /** Builder for field-specific policy assertions. */
153
+ export declare class FieldBuilder implements Builder {
154
+ constructor(path: string)
155
+
156
+ equal(value: string | number): AstNode
157
+ eq(value: string | number): AstNode
158
+ notEqual(value: string | number): AstNode
159
+ neq(value: string | number): AstNode
160
+
161
+ greaterThan(value: string | number): AstNode
162
+ gt(value: string | number): AstNode
163
+ greaterThanOrEqual(value: string | number): AstNode
164
+ gte(value: string | number): AstNode
165
+ lessThan(value: string | number): AstNode
166
+ lt(value: string | number): AstNode
167
+ lessThanOrEqual(value: string | number): AstNode
168
+ lte(value: string | number): AstNode
169
+
170
+ in(values: string[]): AstNode
171
+ contains(value: string): AstNode
172
+
173
+ build(): AstNode
174
+ toJSON(): AstNode
175
+ }
176
+
177
+ /** Builder for Semantic Vibe Engine checks. */
178
+ export declare class VibeBuilder implements Builder {
179
+ constructor(intent: string)
180
+
181
+ withConfidence(threshold: number): this
182
+
183
+ build(): AstNode
184
+ toJSON(): AstNode
30
185
  }
186
+
187
+ /** Entry point for the fluent policy builder API. */
188
+ export declare class PolicyBuilder {
189
+ static field(path: string): FieldBuilder
190
+ static and(...nodes: PolicyInput[]): AstNode
191
+ static or(...nodes: PolicyInput[]): AstNode
192
+ static not(node: PolicyInput): AstNode
193
+ static exists(path: string): AstNode
194
+ static literalTrue(): AstNode
195
+ static literalFalse(): AstNode
196
+ static vibe(intent: string): VibeBuilder
197
+ }
198
+
199
+ /** Shorthand alias for {@link PolicyBuilder}. */
200
+ export declare const p: typeof PolicyBuilder
package/index.js CHANGED
@@ -38,14 +38,25 @@ function loadNative() {
38
38
  )
39
39
  }
40
40
 
41
- const nativeAddon = loadNative()
41
+ const { p, PolicyBuilder, FieldBuilder, VibeBuilder } = require('./lib/policy-builder')
42
42
 
43
- module.exports = nativeAddon
43
+ let native
44
+ try {
45
+ native = loadNative()
46
+ } catch (_) {
47
+ native = {}
48
+ }
49
+
50
+ module.exports = { ...native, p, PolicyBuilder, FieldBuilder, VibeBuilder }
44
51
 
45
52
  // Explicitly assign named exports so Node.js CJS-ESM bridge can statically analyze them
46
- module.exports.JsGp2FServer = nativeAddon.JsGp2FServer
47
- module.exports.JsWorkflow = nativeAddon.JsWorkflow
48
- module.exports.NodeKind = nativeAddon.NodeKind
49
- module.exports.JsServerConfig = nativeAddon.JsServerConfig
50
- module.exports.JsActivityConfig = nativeAddon.JsActivityConfig
51
- module.exports.JsAstNode = nativeAddon.JsAstNode
53
+ module.exports.GP2FServer = native.JsGp2FServer || native.GP2FServer
54
+ module.exports.Workflow = native.JsWorkflow || native.Workflow
55
+ module.exports.AstNode = native.JsAstNode || native.AstNode
56
+ module.exports.NodeKind = native.JsNodeKind || native.NodeKind
57
+ module.exports.evaluate = native.evaluate
58
+ module.exports.evaluateWithTrace = native.evaluateWithTrace
59
+ module.exports.p = p
60
+ module.exports.PolicyBuilder = PolicyBuilder
61
+ module.exports.FieldBuilder = FieldBuilder
62
+ module.exports.VibeBuilder = VibeBuilder
@@ -0,0 +1,148 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Fluent Policy Builder API for constructing GP2F policy AST nodes.
5
+ *
6
+ * Provides a chainable alternative to writing raw JSON AST objects.
7
+ *
8
+ * @example
9
+ * ```js
10
+ * const { p } = require('@gp2f/server');
11
+ *
12
+ * const policy = p.and(
13
+ * p.field('/user/role').eq('admin'),
14
+ * p.exists('/session/token'),
15
+ * );
16
+ * ```
17
+ */
18
+
19
+ // ── Internal helper ───────────────────────────────────────────────────────────
20
+
21
+ function resolve(node) {
22
+ return node && typeof node.build === 'function' ? node.build() : node
23
+ }
24
+
25
+ // ── FieldBuilder ──────────────────────────────────────────────────────────────
26
+
27
+ class FieldBuilder {
28
+ constructor(path) {
29
+ this._path = path
30
+ }
31
+
32
+ _op(kind, value) {
33
+ return {
34
+ kind,
35
+ children: [{ kind: 'Field', path: this._path }],
36
+ value: String(value)
37
+ }
38
+ }
39
+
40
+ // Equality
41
+ equal(value) { return this._op('Eq', value) }
42
+ eq(value) { return this.equal(value) }
43
+ notEqual(value) { return this._op('Neq', value) }
44
+ neq(value) { return this.notEqual(value) }
45
+
46
+ // Comparisons
47
+ greaterThan(value) { return this._op('Gt', value) }
48
+ gt(value) { return this.greaterThan(value) }
49
+ greaterThanOrEqual(value) { return this._op('Gte', value) }
50
+ gte(value) { return this.greaterThanOrEqual(value) }
51
+ lessThan(value) { return this._op('Lt', value) }
52
+ lt(value) { return this.lessThan(value) }
53
+ lessThanOrEqual(value) { return this._op('Lte', value) }
54
+ lte(value) { return this.lessThanOrEqual(value) }
55
+
56
+ // Collection
57
+ in(values) {
58
+ return {
59
+ kind: 'In',
60
+ children: [{ kind: 'Field', path: this._path }],
61
+ value: JSON.stringify(values)
62
+ }
63
+ }
64
+
65
+ contains(value) {
66
+ return {
67
+ kind: 'Contains',
68
+ children: [{ kind: 'Field', path: this._path }],
69
+ value: String(value)
70
+ }
71
+ }
72
+
73
+ build() {
74
+ throw new Error(
75
+ 'FieldBuilder must be terminated with an operator (e.g. .eq(), .gt())'
76
+ )
77
+ }
78
+
79
+ toJSON() {
80
+ return this.build()
81
+ }
82
+ }
83
+
84
+ // ── VibeBuilder ───────────────────────────────────────────────────────────────
85
+
86
+ class VibeBuilder {
87
+ constructor(intent) {
88
+ this._intent = intent
89
+ this._threshold = undefined
90
+ }
91
+
92
+ withConfidence(threshold) {
93
+ this._threshold = threshold
94
+ return this
95
+ }
96
+
97
+ build() {
98
+ const node = { kind: 'VibeCheck', value: this._intent }
99
+ if (this._threshold !== undefined) {
100
+ node.path = String(this._threshold)
101
+ }
102
+ return node
103
+ }
104
+
105
+ toJSON() {
106
+ return this.build()
107
+ }
108
+ }
109
+
110
+ // ── PolicyBuilder ─────────────────────────────────────────────────────────────
111
+
112
+ class PolicyBuilder {
113
+ static field(path) {
114
+ return new FieldBuilder(path)
115
+ }
116
+
117
+ static and(...nodes) {
118
+ return { kind: 'And', children: nodes.map(resolve) }
119
+ }
120
+
121
+ static or(...nodes) {
122
+ return { kind: 'Or', children: nodes.map(resolve) }
123
+ }
124
+
125
+ static not(node) {
126
+ return { kind: 'Not', children: [resolve(node)] }
127
+ }
128
+
129
+ static exists(path) {
130
+ return { kind: 'Exists', path }
131
+ }
132
+
133
+ static literalTrue() {
134
+ return { kind: 'LiteralTrue' }
135
+ }
136
+
137
+ static literalFalse() {
138
+ return { kind: 'LiteralFalse' }
139
+ }
140
+
141
+ static vibe(intent) {
142
+ return new VibeBuilder(intent)
143
+ }
144
+ }
145
+
146
+ const p = PolicyBuilder
147
+
148
+ module.exports = { p, PolicyBuilder, FieldBuilder, VibeBuilder }
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@gp2f/server",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
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",
7
7
  "files": [
8
8
  "index.js",
9
9
  "index.d.ts",
10
+ "lib",
10
11
  "gp2f_node.*.node"
11
12
  ],
12
13
  "scripts": {