@nextera.one/axis-server-sdk 0.1.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/dist/index.d.mts +59 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +233 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +206 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +62 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { OnModuleInit } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
declare const HANDLER_METADATA_KEY = "axis:handler";
|
|
4
|
+
declare function Handler(intent?: string): ClassDecorator;
|
|
5
|
+
|
|
6
|
+
declare const INTENT_ROUTES_KEY = "axis:intent_routes";
|
|
7
|
+
interface IntentRoute {
|
|
8
|
+
action: string;
|
|
9
|
+
methodName: string | symbol;
|
|
10
|
+
absolute?: boolean;
|
|
11
|
+
frame?: boolean;
|
|
12
|
+
}
|
|
13
|
+
interface IntentOptions {
|
|
14
|
+
absolute?: boolean;
|
|
15
|
+
frame?: boolean;
|
|
16
|
+
}
|
|
17
|
+
declare function Intent(action: string, options?: IntentOptions): MethodDecorator;
|
|
18
|
+
|
|
19
|
+
interface AxisFrame {
|
|
20
|
+
flags: number;
|
|
21
|
+
headers: Map<number, Uint8Array>;
|
|
22
|
+
body: Uint8Array;
|
|
23
|
+
sig: Uint8Array;
|
|
24
|
+
metadata?: Record<string, any>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface AxisEffect {
|
|
28
|
+
ok: boolean;
|
|
29
|
+
effect: string;
|
|
30
|
+
body?: Uint8Array;
|
|
31
|
+
headers?: Map<number, Uint8Array>;
|
|
32
|
+
metadata?: any;
|
|
33
|
+
}
|
|
34
|
+
declare class IntentRouter {
|
|
35
|
+
private handlers;
|
|
36
|
+
register(intent: string, handler: any): void;
|
|
37
|
+
registerHandler(instance: any): void;
|
|
38
|
+
route(frame: AxisFrame): Promise<AxisEffect>;
|
|
39
|
+
private recordLatency;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface AxisHandler {
|
|
43
|
+
readonly name: string;
|
|
44
|
+
readonly open?: boolean;
|
|
45
|
+
readonly description?: string;
|
|
46
|
+
readonly execute?: (body: Uint8Array, headers?: Map<number, Uint8Array>) => Promise<Uint8Array | any>;
|
|
47
|
+
}
|
|
48
|
+
interface AxisHandlerInit extends AxisHandler, OnModuleInit {
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface AxisCrudHandler extends AxisHandlerInit {
|
|
52
|
+
create(body: Uint8Array, headers?: Map<number, Uint8Array>): Promise<Uint8Array>;
|
|
53
|
+
findAll(body: Uint8Array, headers?: Map<number, Uint8Array>): Promise<Uint8Array>;
|
|
54
|
+
findOne(body: Uint8Array, headers?: Map<number, Uint8Array>): Promise<Uint8Array>;
|
|
55
|
+
update(body: Uint8Array, headers?: Map<number, Uint8Array>): Promise<Uint8Array>;
|
|
56
|
+
remove(body: Uint8Array, headers?: Map<number, Uint8Array>): Promise<Uint8Array>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { type AxisCrudHandler, type AxisEffect, type AxisFrame, type AxisHandler, type AxisHandlerInit, HANDLER_METADATA_KEY, Handler, INTENT_ROUTES_KEY, Intent, type IntentOptions, type IntentRoute, IntentRouter };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { OnModuleInit } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
declare const HANDLER_METADATA_KEY = "axis:handler";
|
|
4
|
+
declare function Handler(intent?: string): ClassDecorator;
|
|
5
|
+
|
|
6
|
+
declare const INTENT_ROUTES_KEY = "axis:intent_routes";
|
|
7
|
+
interface IntentRoute {
|
|
8
|
+
action: string;
|
|
9
|
+
methodName: string | symbol;
|
|
10
|
+
absolute?: boolean;
|
|
11
|
+
frame?: boolean;
|
|
12
|
+
}
|
|
13
|
+
interface IntentOptions {
|
|
14
|
+
absolute?: boolean;
|
|
15
|
+
frame?: boolean;
|
|
16
|
+
}
|
|
17
|
+
declare function Intent(action: string, options?: IntentOptions): MethodDecorator;
|
|
18
|
+
|
|
19
|
+
interface AxisFrame {
|
|
20
|
+
flags: number;
|
|
21
|
+
headers: Map<number, Uint8Array>;
|
|
22
|
+
body: Uint8Array;
|
|
23
|
+
sig: Uint8Array;
|
|
24
|
+
metadata?: Record<string, any>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface AxisEffect {
|
|
28
|
+
ok: boolean;
|
|
29
|
+
effect: string;
|
|
30
|
+
body?: Uint8Array;
|
|
31
|
+
headers?: Map<number, Uint8Array>;
|
|
32
|
+
metadata?: any;
|
|
33
|
+
}
|
|
34
|
+
declare class IntentRouter {
|
|
35
|
+
private handlers;
|
|
36
|
+
register(intent: string, handler: any): void;
|
|
37
|
+
registerHandler(instance: any): void;
|
|
38
|
+
route(frame: AxisFrame): Promise<AxisEffect>;
|
|
39
|
+
private recordLatency;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface AxisHandler {
|
|
43
|
+
readonly name: string;
|
|
44
|
+
readonly open?: boolean;
|
|
45
|
+
readonly description?: string;
|
|
46
|
+
readonly execute?: (body: Uint8Array, headers?: Map<number, Uint8Array>) => Promise<Uint8Array | any>;
|
|
47
|
+
}
|
|
48
|
+
interface AxisHandlerInit extends AxisHandler, OnModuleInit {
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface AxisCrudHandler extends AxisHandlerInit {
|
|
52
|
+
create(body: Uint8Array, headers?: Map<number, Uint8Array>): Promise<Uint8Array>;
|
|
53
|
+
findAll(body: Uint8Array, headers?: Map<number, Uint8Array>): Promise<Uint8Array>;
|
|
54
|
+
findOne(body: Uint8Array, headers?: Map<number, Uint8Array>): Promise<Uint8Array>;
|
|
55
|
+
update(body: Uint8Array, headers?: Map<number, Uint8Array>): Promise<Uint8Array>;
|
|
56
|
+
remove(body: Uint8Array, headers?: Map<number, Uint8Array>): Promise<Uint8Array>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { type AxisCrudHandler, type AxisEffect, type AxisFrame, type AxisHandler, type AxisHandlerInit, HANDLER_METADATA_KEY, Handler, INTENT_ROUTES_KEY, Intent, type IntentOptions, type IntentRoute, IntentRouter };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
19
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
20
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
21
|
+
if (decorator = decorators[i])
|
|
22
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
23
|
+
if (kind && result) __defProp(target, key, result);
|
|
24
|
+
return result;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// src/index.ts
|
|
28
|
+
var index_exports = {};
|
|
29
|
+
__export(index_exports, {
|
|
30
|
+
HANDLER_METADATA_KEY: () => HANDLER_METADATA_KEY,
|
|
31
|
+
Handler: () => Handler,
|
|
32
|
+
INTENT_ROUTES_KEY: () => INTENT_ROUTES_KEY,
|
|
33
|
+
Intent: () => Intent,
|
|
34
|
+
IntentRouter: () => IntentRouter
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
|
|
38
|
+
// src/decorators/handler.decorator.ts
|
|
39
|
+
var import_common = require("@nestjs/common");
|
|
40
|
+
var HANDLER_METADATA_KEY = "axis:handler";
|
|
41
|
+
function Handler(intent) {
|
|
42
|
+
return (target) => {
|
|
43
|
+
(0, import_common.SetMetadata)(HANDLER_METADATA_KEY, { intent })(target);
|
|
44
|
+
(0, import_common.Injectable)()(target);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/decorators/intent.decorator.ts
|
|
49
|
+
var import_reflect_metadata = require("reflect-metadata");
|
|
50
|
+
var INTENT_ROUTES_KEY = "axis:intent_routes";
|
|
51
|
+
function Intent(action, options) {
|
|
52
|
+
return (target, propertyKey) => {
|
|
53
|
+
const routes = Reflect.getMetadata(INTENT_ROUTES_KEY, target.constructor) || [];
|
|
54
|
+
routes.push({
|
|
55
|
+
action,
|
|
56
|
+
methodName: propertyKey,
|
|
57
|
+
absolute: options?.absolute,
|
|
58
|
+
frame: options?.frame
|
|
59
|
+
});
|
|
60
|
+
Reflect.defineMetadata(INTENT_ROUTES_KEY, routes, target.constructor);
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/engine/intent.router.ts
|
|
65
|
+
var import_common2 = require("@nestjs/common");
|
|
66
|
+
var IntentRouter = class {
|
|
67
|
+
constructor() {
|
|
68
|
+
/** Internal registry of dynamic intent handlers */
|
|
69
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Registers a handler for a specific intent.
|
|
73
|
+
* Handlers can be functions: `(body, headers) => Promise<Uint8Array | AxisEffect>`
|
|
74
|
+
* Or objects with a method: `handle(frame: AxisFrame) => Promise<AxisEffect>`
|
|
75
|
+
*
|
|
76
|
+
* @param {string} intent - The unique intent identifier (e.g., 'axis.vault.create')
|
|
77
|
+
* @param {any} handler - The handler function or object
|
|
78
|
+
*/
|
|
79
|
+
register(intent, handler) {
|
|
80
|
+
this.handlers.set(intent, handler);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Automatically registers all `@Intent`-decorated methods from a handler instance.
|
|
84
|
+
*
|
|
85
|
+
* Reads the handler prefix from `@Handler` metadata (or falls back to `instance.name`),
|
|
86
|
+
* then registers each `@Intent`-decorated method accordingly.
|
|
87
|
+
*
|
|
88
|
+
* @param {any} instance - The handler instance with `@Intent`-decorated methods
|
|
89
|
+
*/
|
|
90
|
+
registerHandler(instance) {
|
|
91
|
+
const handlerMeta = Reflect.getMetadata(
|
|
92
|
+
HANDLER_METADATA_KEY,
|
|
93
|
+
instance.constructor
|
|
94
|
+
);
|
|
95
|
+
const prefix = handlerMeta?.intent || instance.name;
|
|
96
|
+
const routes = Reflect.getMetadata(INTENT_ROUTES_KEY, instance.constructor) || [];
|
|
97
|
+
for (const route of routes) {
|
|
98
|
+
const intentName = route.absolute ? route.action : `${prefix}.${route.action}`;
|
|
99
|
+
const fn = instance[route.methodName].bind(instance);
|
|
100
|
+
if (route.frame) {
|
|
101
|
+
this.register(intentName, { handle: fn });
|
|
102
|
+
} else {
|
|
103
|
+
this.register(intentName, fn);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Routes a decoded AXIS frame to the appropriate handler.
|
|
109
|
+
*
|
|
110
|
+
* **Precedence:**
|
|
111
|
+
* 1. System Built-ins (`system.ping`, `public.ping`, `system.time`, `system.echo`)
|
|
112
|
+
* 2. Meta-intent execution (`INTENT.EXEC` / `axis.intent.exec`)
|
|
113
|
+
* 3. Dynamically registered handlers from modules.
|
|
114
|
+
*
|
|
115
|
+
* @param {AxisFrame} frame - The validated and decoded binary frame
|
|
116
|
+
* @returns {Promise<AxisEffect>} The resulting effect of the execution
|
|
117
|
+
* @throws {Error} If the intent header is missing or no handler is registered
|
|
118
|
+
*/
|
|
119
|
+
async route(frame) {
|
|
120
|
+
const start = process.hrtime();
|
|
121
|
+
let intent = "unknown";
|
|
122
|
+
try {
|
|
123
|
+
const intentBytes = frame.headers.get(3);
|
|
124
|
+
if (!intentBytes) throw new Error("Missing intent");
|
|
125
|
+
intent = new TextDecoder().decode(intentBytes);
|
|
126
|
+
let effect;
|
|
127
|
+
if (intent === "system.ping" || intent === "public.ping") {
|
|
128
|
+
effect = {
|
|
129
|
+
ok: true,
|
|
130
|
+
effect: "pong",
|
|
131
|
+
headers: /* @__PURE__ */ new Map([
|
|
132
|
+
[100, new TextEncoder().encode("AXIS_BACKEND_V1")]
|
|
133
|
+
]),
|
|
134
|
+
body: new TextEncoder().encode(
|
|
135
|
+
JSON.stringify({
|
|
136
|
+
status: "ok",
|
|
137
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
138
|
+
version: "1.0.0"
|
|
139
|
+
})
|
|
140
|
+
)
|
|
141
|
+
};
|
|
142
|
+
} else if (intent === "system.time") {
|
|
143
|
+
const ts = Date.now().toString();
|
|
144
|
+
effect = {
|
|
145
|
+
ok: true,
|
|
146
|
+
effect: "time",
|
|
147
|
+
body: new TextEncoder().encode(
|
|
148
|
+
JSON.stringify({
|
|
149
|
+
ts,
|
|
150
|
+
iso: (/* @__PURE__ */ new Date()).toISOString()
|
|
151
|
+
})
|
|
152
|
+
)
|
|
153
|
+
};
|
|
154
|
+
} else if (intent === "system.echo") {
|
|
155
|
+
effect = {
|
|
156
|
+
ok: true,
|
|
157
|
+
effect: "echo",
|
|
158
|
+
body: frame.body
|
|
159
|
+
};
|
|
160
|
+
} else if (intent === "INTENT.EXEC" || intent === "axis.intent.exec") {
|
|
161
|
+
try {
|
|
162
|
+
const bodyJSON = JSON.parse(new TextDecoder().decode(frame.body));
|
|
163
|
+
const innerIntent = bodyJSON.intent;
|
|
164
|
+
const innerArgs = bodyJSON.args || {};
|
|
165
|
+
if (!innerIntent) {
|
|
166
|
+
throw new Error("INTENT.EXEC missing inner intent");
|
|
167
|
+
}
|
|
168
|
+
const innerFrame = {
|
|
169
|
+
...frame,
|
|
170
|
+
headers: new Map(frame.headers),
|
|
171
|
+
body: new TextEncoder().encode(JSON.stringify(innerArgs))
|
|
172
|
+
};
|
|
173
|
+
innerFrame.headers.set(3, new TextEncoder().encode(innerIntent));
|
|
174
|
+
return await this.route(innerFrame);
|
|
175
|
+
} catch (e) {
|
|
176
|
+
throw new Error(`INTENT.EXEC unwrapping failed: ${e.message}`);
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
const handler = this.handlers.get(intent);
|
|
180
|
+
if (!handler) {
|
|
181
|
+
throw new Error(`Intent not found: ${intent}`);
|
|
182
|
+
}
|
|
183
|
+
if (typeof handler === "function") {
|
|
184
|
+
const resultBody = await handler(frame.body, frame.headers);
|
|
185
|
+
effect = {
|
|
186
|
+
ok: true,
|
|
187
|
+
effect: "complete",
|
|
188
|
+
body: resultBody
|
|
189
|
+
};
|
|
190
|
+
} else {
|
|
191
|
+
if (typeof handler.handle === "function") {
|
|
192
|
+
effect = await handler.handle(frame);
|
|
193
|
+
} else if (typeof handler.execute === "function") {
|
|
194
|
+
const bodyRes = await handler.execute(
|
|
195
|
+
frame.body,
|
|
196
|
+
frame.headers
|
|
197
|
+
);
|
|
198
|
+
effect = {
|
|
199
|
+
ok: true,
|
|
200
|
+
effect: "complete",
|
|
201
|
+
body: bodyRes
|
|
202
|
+
};
|
|
203
|
+
} else {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`Handler for ${intent} does not implement handle or execute`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
this.recordLatency(intent, start);
|
|
211
|
+
return effect;
|
|
212
|
+
} catch (e) {
|
|
213
|
+
console.error(`Error routing intent ${intent}:`, e.message);
|
|
214
|
+
throw e;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
recordLatency(intent, start) {
|
|
218
|
+
const diff = process.hrtime(start);
|
|
219
|
+
void diff;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
IntentRouter = __decorateClass([
|
|
223
|
+
(0, import_common2.Injectable)()
|
|
224
|
+
], IntentRouter);
|
|
225
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
226
|
+
0 && (module.exports = {
|
|
227
|
+
HANDLER_METADATA_KEY,
|
|
228
|
+
Handler,
|
|
229
|
+
INTENT_ROUTES_KEY,
|
|
230
|
+
Intent,
|
|
231
|
+
IntentRouter
|
|
232
|
+
});
|
|
233
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/decorators/handler.decorator.ts","../src/decorators/intent.decorator.ts","../src/engine/intent.router.ts"],"sourcesContent":["// Decorators\nexport { Handler, HANDLER_METADATA_KEY } from './decorators/handler.decorator';\nexport {\n Intent,\n INTENT_ROUTES_KEY,\n IntentRoute,\n IntentOptions,\n} from './decorators/intent.decorator';\n\n// Engine\nexport { IntentRouter, AxisEffect } from './engine/intent.router';\n\n// Interfaces\nexport { AxisHandler, AxisHandlerInit } from './interfaces/axis-handler.interface';\nexport { AxisCrudHandler } from './interfaces/axis-crud-handler.interface';\n\n// Types\nexport { AxisFrame } from './types/axis-frame';\n","import { Injectable, SetMetadata } from '@nestjs/common';\n\nexport const HANDLER_METADATA_KEY = 'axis:handler';\n\n/**\n * Decorator to mark a class as an Axis Handler.\n * Handlers are responsible for processing intents or specific logic\n * for Axis modules.\n */\nexport function Handler(intent?: string): ClassDecorator {\n return (target: Function) => {\n SetMetadata(HANDLER_METADATA_KEY, { intent })(target);\n Injectable()(target as any);\n };\n}\n","import 'reflect-metadata';\n\nexport const INTENT_ROUTES_KEY = 'axis:intent_routes';\n\nexport interface IntentRoute {\n action: string;\n methodName: string | symbol;\n absolute?: boolean;\n frame?: boolean;\n}\n\nexport interface IntentOptions {\n /** If true, the action is the full intent name (not prefixed with handler name) */\n absolute?: boolean;\n /** If true, register as { handle: fn } for frame-based handlers */\n frame?: boolean;\n}\n\n/**\n * Marks a method as an intent handler.\n *\n * The full intent name is resolved as `{handler_prefix}.{action}` by default.\n * Use `{ absolute: true }` to use the action string as the full intent name.\n * Use `{ frame: true }` for handlers that receive an AxisFrame and return AxisEffect.\n *\n * @example\n * ```ts\n * @Handler('axis.actor_keys')\n * class MyHandler {\n * @Intent('create')\n * async create(body: Uint8Array) { ... }\n *\n * @Intent('public.auth.capsule.issue', { absolute: true })\n * async publicIssue(body: Uint8Array) { ... }\n *\n * @Intent('presence.resume', { frame: true })\n * async handlePresence(frame: AxisFrame) { ... }\n * }\n * ```\n */\nexport function Intent(action: string, options?: IntentOptions): MethodDecorator {\n return (target, propertyKey) => {\n const routes: IntentRoute[] =\n Reflect.getMetadata(INTENT_ROUTES_KEY, target.constructor) || [];\n routes.push({\n action,\n methodName: propertyKey,\n absolute: options?.absolute,\n frame: options?.frame,\n });\n Reflect.defineMetadata(INTENT_ROUTES_KEY, routes, target.constructor);\n };\n}\n","import { Injectable } from '@nestjs/common';\nimport { AxisFrame } from '../types/axis-frame';\nimport { HANDLER_METADATA_KEY } from '../decorators/handler.decorator';\nimport { INTENT_ROUTES_KEY, IntentRoute } from '../decorators/intent.decorator';\n\n/**\n * Represents the outcome of an AXIS intent execution.\n *\n * @interface AxisEffect\n */\nexport interface AxisEffect {\n /** Whether the intent was processed successfully at the application level */\n ok: boolean;\n /** A descriptive string classifier for the result (e.g., 'FILE_CREATED', 'PONG') */\n effect: string;\n /** Optional binary payload (body) to be returned to the requester */\n body?: Uint8Array;\n /** Optional custom TLV headers to be included in the response frame */\n headers?: Map<number, Uint8Array>;\n /** Optional metadata for internal logging or audit (not sent to client) */\n metadata?: any;\n}\n\n/**\n * IntentRouter\n *\n * The central dispatching mechanism of the AXIS backend.\n * Maps binary intents (identified by their name in the header) to specialized handlers.\n *\n * **Features:**\n * 1. **Built-in Fast Path:** Handles high-frequency system intents (ping, time, echo) directly.\n * 2. **Dynamic Handler Registration:** Allows modules to register handlers at runtime.\n * 3. **Decorator-driven Registration:** Uses {@link registerHandler} to auto-register `@Intent`-decorated methods.\n * 4. **Polymorphic Handlers:** Supports both raw function handlers and object-based `{ handle }` handlers.\n *\n * @class IntentRouter\n */\n@Injectable()\nexport class IntentRouter {\n /** Internal registry of dynamic intent handlers */\n private handlers = new Map<string, any>();\n\n /**\n * Registers a handler for a specific intent.\n * Handlers can be functions: `(body, headers) => Promise<Uint8Array | AxisEffect>`\n * Or objects with a method: `handle(frame: AxisFrame) => Promise<AxisEffect>`\n *\n * @param {string} intent - The unique intent identifier (e.g., 'axis.vault.create')\n * @param {any} handler - The handler function or object\n */\n register(intent: string, handler: any) {\n this.handlers.set(intent, handler);\n }\n\n /**\n * Automatically registers all `@Intent`-decorated methods from a handler instance.\n *\n * Reads the handler prefix from `@Handler` metadata (or falls back to `instance.name`),\n * then registers each `@Intent`-decorated method accordingly.\n *\n * @param {any} instance - The handler instance with `@Intent`-decorated methods\n */\n registerHandler(instance: any) {\n const handlerMeta = Reflect.getMetadata(\n HANDLER_METADATA_KEY,\n instance.constructor,\n );\n const prefix: string | undefined = handlerMeta?.intent || instance.name;\n\n const routes: IntentRoute[] =\n Reflect.getMetadata(INTENT_ROUTES_KEY, instance.constructor) || [];\n\n for (const route of routes) {\n const intentName = route.absolute\n ? route.action\n : `${prefix}.${route.action}`;\n const fn = instance[route.methodName].bind(instance);\n\n if (route.frame) {\n this.register(intentName, { handle: fn });\n } else {\n this.register(intentName, fn);\n }\n }\n }\n\n /**\n * Routes a decoded AXIS frame to the appropriate handler.\n *\n * **Precedence:**\n * 1. System Built-ins (`system.ping`, `public.ping`, `system.time`, `system.echo`)\n * 2. Meta-intent execution (`INTENT.EXEC` / `axis.intent.exec`)\n * 3. Dynamically registered handlers from modules.\n *\n * @param {AxisFrame} frame - The validated and decoded binary frame\n * @returns {Promise<AxisEffect>} The resulting effect of the execution\n * @throws {Error} If the intent header is missing or no handler is registered\n */\n async route(frame: AxisFrame): Promise<AxisEffect> {\n const start = process.hrtime();\n let intent = 'unknown';\n\n try {\n // Extract intent from header TLV (tag 3 = TLV_INTENT)\n const intentBytes = frame.headers.get(3);\n if (!intentBytes) throw new Error('Missing intent');\n intent = new TextDecoder().decode(intentBytes);\n\n let effect: AxisEffect;\n\n if (intent === 'system.ping' || intent === 'public.ping') {\n effect = {\n ok: true,\n effect: 'pong',\n headers: new Map([\n [100, new TextEncoder().encode('AXIS_BACKEND_V1')],\n ]),\n body: new TextEncoder().encode(\n JSON.stringify({\n status: 'ok',\n timestamp: new Date().toISOString(),\n version: '1.0.0',\n }),\n ),\n };\n } else if (intent === 'system.time') {\n const ts = Date.now().toString();\n effect = {\n ok: true,\n effect: 'time',\n body: new TextEncoder().encode(\n JSON.stringify({\n ts,\n iso: new Date().toISOString(),\n }),\n ),\n };\n } else if (intent === 'system.echo') {\n effect = {\n ok: true,\n effect: 'echo',\n body: frame.body,\n };\n } else if (intent === 'INTENT.EXEC' || intent === 'axis.intent.exec') {\n // Meta-intent: Unwrap and execute the inner intent\n try {\n const bodyJSON = JSON.parse(new TextDecoder().decode(frame.body));\n const innerIntent = bodyJSON.intent;\n const innerArgs = bodyJSON.args || {};\n\n if (!innerIntent) {\n throw new Error('INTENT.EXEC missing inner intent');\n }\n\n const innerFrame: AxisFrame = {\n ...frame,\n headers: new Map(frame.headers),\n body: new TextEncoder().encode(JSON.stringify(innerArgs)),\n };\n innerFrame.headers.set(3, new TextEncoder().encode(innerIntent));\n\n return await this.route(innerFrame);\n } catch (e: any) {\n throw new Error(`INTENT.EXEC unwrapping failed: ${e.message}`);\n }\n } else {\n const handler = this.handlers.get(intent);\n if (!handler) {\n throw new Error(`Intent not found: ${intent}`);\n }\n\n if (typeof handler === 'function') {\n const resultBody = await handler(frame.body, frame.headers);\n effect = {\n ok: true,\n effect: 'complete',\n body: resultBody,\n };\n } else {\n if (typeof (handler as any).handle === 'function') {\n effect = await (handler as any).handle(frame);\n } else if (typeof (handler as any).execute === 'function') {\n const bodyRes = await (handler as any).execute(\n frame.body,\n frame.headers,\n );\n effect = {\n ok: true,\n effect: 'complete',\n body: bodyRes,\n };\n } else {\n throw new Error(\n `Handler for ${intent} does not implement handle or execute`,\n );\n }\n }\n }\n\n this.recordLatency(intent, start);\n return effect;\n } catch (e: any) {\n console.error(`Error routing intent ${intent}:`, e.message);\n throw e;\n }\n }\n\n private recordLatency(intent: string, start: [number, number]) {\n const diff = process.hrtime(start);\n // Available for subclass telemetry hooks or future logging\n void diff;\n }\n}\n"],"mappings":"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","names":["import_common"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result) __defProp(target, key, result);
|
|
9
|
+
return result;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/decorators/handler.decorator.ts
|
|
13
|
+
import { Injectable, SetMetadata } from "@nestjs/common";
|
|
14
|
+
var HANDLER_METADATA_KEY = "axis:handler";
|
|
15
|
+
function Handler(intent) {
|
|
16
|
+
return (target) => {
|
|
17
|
+
SetMetadata(HANDLER_METADATA_KEY, { intent })(target);
|
|
18
|
+
Injectable()(target);
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/decorators/intent.decorator.ts
|
|
23
|
+
import "reflect-metadata";
|
|
24
|
+
var INTENT_ROUTES_KEY = "axis:intent_routes";
|
|
25
|
+
function Intent(action, options) {
|
|
26
|
+
return (target, propertyKey) => {
|
|
27
|
+
const routes = Reflect.getMetadata(INTENT_ROUTES_KEY, target.constructor) || [];
|
|
28
|
+
routes.push({
|
|
29
|
+
action,
|
|
30
|
+
methodName: propertyKey,
|
|
31
|
+
absolute: options?.absolute,
|
|
32
|
+
frame: options?.frame
|
|
33
|
+
});
|
|
34
|
+
Reflect.defineMetadata(INTENT_ROUTES_KEY, routes, target.constructor);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/engine/intent.router.ts
|
|
39
|
+
import { Injectable as Injectable2 } from "@nestjs/common";
|
|
40
|
+
var IntentRouter = class {
|
|
41
|
+
constructor() {
|
|
42
|
+
/** Internal registry of dynamic intent handlers */
|
|
43
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Registers a handler for a specific intent.
|
|
47
|
+
* Handlers can be functions: `(body, headers) => Promise<Uint8Array | AxisEffect>`
|
|
48
|
+
* Or objects with a method: `handle(frame: AxisFrame) => Promise<AxisEffect>`
|
|
49
|
+
*
|
|
50
|
+
* @param {string} intent - The unique intent identifier (e.g., 'axis.vault.create')
|
|
51
|
+
* @param {any} handler - The handler function or object
|
|
52
|
+
*/
|
|
53
|
+
register(intent, handler) {
|
|
54
|
+
this.handlers.set(intent, handler);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Automatically registers all `@Intent`-decorated methods from a handler instance.
|
|
58
|
+
*
|
|
59
|
+
* Reads the handler prefix from `@Handler` metadata (or falls back to `instance.name`),
|
|
60
|
+
* then registers each `@Intent`-decorated method accordingly.
|
|
61
|
+
*
|
|
62
|
+
* @param {any} instance - The handler instance with `@Intent`-decorated methods
|
|
63
|
+
*/
|
|
64
|
+
registerHandler(instance) {
|
|
65
|
+
const handlerMeta = Reflect.getMetadata(
|
|
66
|
+
HANDLER_METADATA_KEY,
|
|
67
|
+
instance.constructor
|
|
68
|
+
);
|
|
69
|
+
const prefix = handlerMeta?.intent || instance.name;
|
|
70
|
+
const routes = Reflect.getMetadata(INTENT_ROUTES_KEY, instance.constructor) || [];
|
|
71
|
+
for (const route of routes) {
|
|
72
|
+
const intentName = route.absolute ? route.action : `${prefix}.${route.action}`;
|
|
73
|
+
const fn = instance[route.methodName].bind(instance);
|
|
74
|
+
if (route.frame) {
|
|
75
|
+
this.register(intentName, { handle: fn });
|
|
76
|
+
} else {
|
|
77
|
+
this.register(intentName, fn);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Routes a decoded AXIS frame to the appropriate handler.
|
|
83
|
+
*
|
|
84
|
+
* **Precedence:**
|
|
85
|
+
* 1. System Built-ins (`system.ping`, `public.ping`, `system.time`, `system.echo`)
|
|
86
|
+
* 2. Meta-intent execution (`INTENT.EXEC` / `axis.intent.exec`)
|
|
87
|
+
* 3. Dynamically registered handlers from modules.
|
|
88
|
+
*
|
|
89
|
+
* @param {AxisFrame} frame - The validated and decoded binary frame
|
|
90
|
+
* @returns {Promise<AxisEffect>} The resulting effect of the execution
|
|
91
|
+
* @throws {Error} If the intent header is missing or no handler is registered
|
|
92
|
+
*/
|
|
93
|
+
async route(frame) {
|
|
94
|
+
const start = process.hrtime();
|
|
95
|
+
let intent = "unknown";
|
|
96
|
+
try {
|
|
97
|
+
const intentBytes = frame.headers.get(3);
|
|
98
|
+
if (!intentBytes) throw new Error("Missing intent");
|
|
99
|
+
intent = new TextDecoder().decode(intentBytes);
|
|
100
|
+
let effect;
|
|
101
|
+
if (intent === "system.ping" || intent === "public.ping") {
|
|
102
|
+
effect = {
|
|
103
|
+
ok: true,
|
|
104
|
+
effect: "pong",
|
|
105
|
+
headers: /* @__PURE__ */ new Map([
|
|
106
|
+
[100, new TextEncoder().encode("AXIS_BACKEND_V1")]
|
|
107
|
+
]),
|
|
108
|
+
body: new TextEncoder().encode(
|
|
109
|
+
JSON.stringify({
|
|
110
|
+
status: "ok",
|
|
111
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
112
|
+
version: "1.0.0"
|
|
113
|
+
})
|
|
114
|
+
)
|
|
115
|
+
};
|
|
116
|
+
} else if (intent === "system.time") {
|
|
117
|
+
const ts = Date.now().toString();
|
|
118
|
+
effect = {
|
|
119
|
+
ok: true,
|
|
120
|
+
effect: "time",
|
|
121
|
+
body: new TextEncoder().encode(
|
|
122
|
+
JSON.stringify({
|
|
123
|
+
ts,
|
|
124
|
+
iso: (/* @__PURE__ */ new Date()).toISOString()
|
|
125
|
+
})
|
|
126
|
+
)
|
|
127
|
+
};
|
|
128
|
+
} else if (intent === "system.echo") {
|
|
129
|
+
effect = {
|
|
130
|
+
ok: true,
|
|
131
|
+
effect: "echo",
|
|
132
|
+
body: frame.body
|
|
133
|
+
};
|
|
134
|
+
} else if (intent === "INTENT.EXEC" || intent === "axis.intent.exec") {
|
|
135
|
+
try {
|
|
136
|
+
const bodyJSON = JSON.parse(new TextDecoder().decode(frame.body));
|
|
137
|
+
const innerIntent = bodyJSON.intent;
|
|
138
|
+
const innerArgs = bodyJSON.args || {};
|
|
139
|
+
if (!innerIntent) {
|
|
140
|
+
throw new Error("INTENT.EXEC missing inner intent");
|
|
141
|
+
}
|
|
142
|
+
const innerFrame = {
|
|
143
|
+
...frame,
|
|
144
|
+
headers: new Map(frame.headers),
|
|
145
|
+
body: new TextEncoder().encode(JSON.stringify(innerArgs))
|
|
146
|
+
};
|
|
147
|
+
innerFrame.headers.set(3, new TextEncoder().encode(innerIntent));
|
|
148
|
+
return await this.route(innerFrame);
|
|
149
|
+
} catch (e) {
|
|
150
|
+
throw new Error(`INTENT.EXEC unwrapping failed: ${e.message}`);
|
|
151
|
+
}
|
|
152
|
+
} else {
|
|
153
|
+
const handler = this.handlers.get(intent);
|
|
154
|
+
if (!handler) {
|
|
155
|
+
throw new Error(`Intent not found: ${intent}`);
|
|
156
|
+
}
|
|
157
|
+
if (typeof handler === "function") {
|
|
158
|
+
const resultBody = await handler(frame.body, frame.headers);
|
|
159
|
+
effect = {
|
|
160
|
+
ok: true,
|
|
161
|
+
effect: "complete",
|
|
162
|
+
body: resultBody
|
|
163
|
+
};
|
|
164
|
+
} else {
|
|
165
|
+
if (typeof handler.handle === "function") {
|
|
166
|
+
effect = await handler.handle(frame);
|
|
167
|
+
} else if (typeof handler.execute === "function") {
|
|
168
|
+
const bodyRes = await handler.execute(
|
|
169
|
+
frame.body,
|
|
170
|
+
frame.headers
|
|
171
|
+
);
|
|
172
|
+
effect = {
|
|
173
|
+
ok: true,
|
|
174
|
+
effect: "complete",
|
|
175
|
+
body: bodyRes
|
|
176
|
+
};
|
|
177
|
+
} else {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`Handler for ${intent} does not implement handle or execute`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
this.recordLatency(intent, start);
|
|
185
|
+
return effect;
|
|
186
|
+
} catch (e) {
|
|
187
|
+
console.error(`Error routing intent ${intent}:`, e.message);
|
|
188
|
+
throw e;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
recordLatency(intent, start) {
|
|
192
|
+
const diff = process.hrtime(start);
|
|
193
|
+
void diff;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
IntentRouter = __decorateClass([
|
|
197
|
+
Injectable2()
|
|
198
|
+
], IntentRouter);
|
|
199
|
+
export {
|
|
200
|
+
HANDLER_METADATA_KEY,
|
|
201
|
+
Handler,
|
|
202
|
+
INTENT_ROUTES_KEY,
|
|
203
|
+
Intent,
|
|
204
|
+
IntentRouter
|
|
205
|
+
};
|
|
206
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/decorators/handler.decorator.ts","../src/decorators/intent.decorator.ts","../src/engine/intent.router.ts"],"sourcesContent":["import { Injectable, SetMetadata } from '@nestjs/common';\n\nexport const HANDLER_METADATA_KEY = 'axis:handler';\n\n/**\n * Decorator to mark a class as an Axis Handler.\n * Handlers are responsible for processing intents or specific logic\n * for Axis modules.\n */\nexport function Handler(intent?: string): ClassDecorator {\n return (target: Function) => {\n SetMetadata(HANDLER_METADATA_KEY, { intent })(target);\n Injectable()(target as any);\n };\n}\n","import 'reflect-metadata';\n\nexport const INTENT_ROUTES_KEY = 'axis:intent_routes';\n\nexport interface IntentRoute {\n action: string;\n methodName: string | symbol;\n absolute?: boolean;\n frame?: boolean;\n}\n\nexport interface IntentOptions {\n /** If true, the action is the full intent name (not prefixed with handler name) */\n absolute?: boolean;\n /** If true, register as { handle: fn } for frame-based handlers */\n frame?: boolean;\n}\n\n/**\n * Marks a method as an intent handler.\n *\n * The full intent name is resolved as `{handler_prefix}.{action}` by default.\n * Use `{ absolute: true }` to use the action string as the full intent name.\n * Use `{ frame: true }` for handlers that receive an AxisFrame and return AxisEffect.\n *\n * @example\n * ```ts\n * @Handler('axis.actor_keys')\n * class MyHandler {\n * @Intent('create')\n * async create(body: Uint8Array) { ... }\n *\n * @Intent('public.auth.capsule.issue', { absolute: true })\n * async publicIssue(body: Uint8Array) { ... }\n *\n * @Intent('presence.resume', { frame: true })\n * async handlePresence(frame: AxisFrame) { ... }\n * }\n * ```\n */\nexport function Intent(action: string, options?: IntentOptions): MethodDecorator {\n return (target, propertyKey) => {\n const routes: IntentRoute[] =\n Reflect.getMetadata(INTENT_ROUTES_KEY, target.constructor) || [];\n routes.push({\n action,\n methodName: propertyKey,\n absolute: options?.absolute,\n frame: options?.frame,\n });\n Reflect.defineMetadata(INTENT_ROUTES_KEY, routes, target.constructor);\n };\n}\n","import { Injectable } from '@nestjs/common';\nimport { AxisFrame } from '../types/axis-frame';\nimport { HANDLER_METADATA_KEY } from '../decorators/handler.decorator';\nimport { INTENT_ROUTES_KEY, IntentRoute } from '../decorators/intent.decorator';\n\n/**\n * Represents the outcome of an AXIS intent execution.\n *\n * @interface AxisEffect\n */\nexport interface AxisEffect {\n /** Whether the intent was processed successfully at the application level */\n ok: boolean;\n /** A descriptive string classifier for the result (e.g., 'FILE_CREATED', 'PONG') */\n effect: string;\n /** Optional binary payload (body) to be returned to the requester */\n body?: Uint8Array;\n /** Optional custom TLV headers to be included in the response frame */\n headers?: Map<number, Uint8Array>;\n /** Optional metadata for internal logging or audit (not sent to client) */\n metadata?: any;\n}\n\n/**\n * IntentRouter\n *\n * The central dispatching mechanism of the AXIS backend.\n * Maps binary intents (identified by their name in the header) to specialized handlers.\n *\n * **Features:**\n * 1. **Built-in Fast Path:** Handles high-frequency system intents (ping, time, echo) directly.\n * 2. **Dynamic Handler Registration:** Allows modules to register handlers at runtime.\n * 3. **Decorator-driven Registration:** Uses {@link registerHandler} to auto-register `@Intent`-decorated methods.\n * 4. **Polymorphic Handlers:** Supports both raw function handlers and object-based `{ handle }` handlers.\n *\n * @class IntentRouter\n */\n@Injectable()\nexport class IntentRouter {\n /** Internal registry of dynamic intent handlers */\n private handlers = new Map<string, any>();\n\n /**\n * Registers a handler for a specific intent.\n * Handlers can be functions: `(body, headers) => Promise<Uint8Array | AxisEffect>`\n * Or objects with a method: `handle(frame: AxisFrame) => Promise<AxisEffect>`\n *\n * @param {string} intent - The unique intent identifier (e.g., 'axis.vault.create')\n * @param {any} handler - The handler function or object\n */\n register(intent: string, handler: any) {\n this.handlers.set(intent, handler);\n }\n\n /**\n * Automatically registers all `@Intent`-decorated methods from a handler instance.\n *\n * Reads the handler prefix from `@Handler` metadata (or falls back to `instance.name`),\n * then registers each `@Intent`-decorated method accordingly.\n *\n * @param {any} instance - The handler instance with `@Intent`-decorated methods\n */\n registerHandler(instance: any) {\n const handlerMeta = Reflect.getMetadata(\n HANDLER_METADATA_KEY,\n instance.constructor,\n );\n const prefix: string | undefined = handlerMeta?.intent || instance.name;\n\n const routes: IntentRoute[] =\n Reflect.getMetadata(INTENT_ROUTES_KEY, instance.constructor) || [];\n\n for (const route of routes) {\n const intentName = route.absolute\n ? route.action\n : `${prefix}.${route.action}`;\n const fn = instance[route.methodName].bind(instance);\n\n if (route.frame) {\n this.register(intentName, { handle: fn });\n } else {\n this.register(intentName, fn);\n }\n }\n }\n\n /**\n * Routes a decoded AXIS frame to the appropriate handler.\n *\n * **Precedence:**\n * 1. System Built-ins (`system.ping`, `public.ping`, `system.time`, `system.echo`)\n * 2. Meta-intent execution (`INTENT.EXEC` / `axis.intent.exec`)\n * 3. Dynamically registered handlers from modules.\n *\n * @param {AxisFrame} frame - The validated and decoded binary frame\n * @returns {Promise<AxisEffect>} The resulting effect of the execution\n * @throws {Error} If the intent header is missing or no handler is registered\n */\n async route(frame: AxisFrame): Promise<AxisEffect> {\n const start = process.hrtime();\n let intent = 'unknown';\n\n try {\n // Extract intent from header TLV (tag 3 = TLV_INTENT)\n const intentBytes = frame.headers.get(3);\n if (!intentBytes) throw new Error('Missing intent');\n intent = new TextDecoder().decode(intentBytes);\n\n let effect: AxisEffect;\n\n if (intent === 'system.ping' || intent === 'public.ping') {\n effect = {\n ok: true,\n effect: 'pong',\n headers: new Map([\n [100, new TextEncoder().encode('AXIS_BACKEND_V1')],\n ]),\n body: new TextEncoder().encode(\n JSON.stringify({\n status: 'ok',\n timestamp: new Date().toISOString(),\n version: '1.0.0',\n }),\n ),\n };\n } else if (intent === 'system.time') {\n const ts = Date.now().toString();\n effect = {\n ok: true,\n effect: 'time',\n body: new TextEncoder().encode(\n JSON.stringify({\n ts,\n iso: new Date().toISOString(),\n }),\n ),\n };\n } else if (intent === 'system.echo') {\n effect = {\n ok: true,\n effect: 'echo',\n body: frame.body,\n };\n } else if (intent === 'INTENT.EXEC' || intent === 'axis.intent.exec') {\n // Meta-intent: Unwrap and execute the inner intent\n try {\n const bodyJSON = JSON.parse(new TextDecoder().decode(frame.body));\n const innerIntent = bodyJSON.intent;\n const innerArgs = bodyJSON.args || {};\n\n if (!innerIntent) {\n throw new Error('INTENT.EXEC missing inner intent');\n }\n\n const innerFrame: AxisFrame = {\n ...frame,\n headers: new Map(frame.headers),\n body: new TextEncoder().encode(JSON.stringify(innerArgs)),\n };\n innerFrame.headers.set(3, new TextEncoder().encode(innerIntent));\n\n return await this.route(innerFrame);\n } catch (e: any) {\n throw new Error(`INTENT.EXEC unwrapping failed: ${e.message}`);\n }\n } else {\n const handler = this.handlers.get(intent);\n if (!handler) {\n throw new Error(`Intent not found: ${intent}`);\n }\n\n if (typeof handler === 'function') {\n const resultBody = await handler(frame.body, frame.headers);\n effect = {\n ok: true,\n effect: 'complete',\n body: resultBody,\n };\n } else {\n if (typeof (handler as any).handle === 'function') {\n effect = await (handler as any).handle(frame);\n } else if (typeof (handler as any).execute === 'function') {\n const bodyRes = await (handler as any).execute(\n frame.body,\n frame.headers,\n );\n effect = {\n ok: true,\n effect: 'complete',\n body: bodyRes,\n };\n } else {\n throw new Error(\n `Handler for ${intent} does not implement handle or execute`,\n );\n }\n }\n }\n\n this.recordLatency(intent, start);\n return effect;\n } catch (e: any) {\n console.error(`Error routing intent ${intent}:`, e.message);\n throw e;\n }\n }\n\n private recordLatency(intent: string, start: [number, number]) {\n const diff = process.hrtime(start);\n // Available for subclass telemetry hooks or future logging\n void diff;\n }\n}\n"],"mappings":"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","names":["Injectable","Injectable"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nextera.one/axis-server-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Axis Protocol server-side SDK — decorators, interfaces, and routing primitives for building Axis handlers",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup",
|
|
22
|
+
"prepublishOnly": "npm run build",
|
|
23
|
+
"dev": "tsup --watch"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/digital-pages-co/axis.git",
|
|
28
|
+
"directory": "axis-server-sdk"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://axisprotocol.io",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/digital-pages-co/axis/issues"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"axis",
|
|
36
|
+
"protocol",
|
|
37
|
+
"typescript",
|
|
38
|
+
"server",
|
|
39
|
+
"sdk",
|
|
40
|
+
"handler",
|
|
41
|
+
"decorator",
|
|
42
|
+
"intent",
|
|
43
|
+
"router",
|
|
44
|
+
"binary"
|
|
45
|
+
],
|
|
46
|
+
"author": "NextEra.One",
|
|
47
|
+
"license": "Apache-2.0",
|
|
48
|
+
"type": "commonjs",
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@nestjs/common": ">=10.0.0",
|
|
51
|
+
"reflect-metadata": ">=0.1.12"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@nestjs/common": "^11.1.12",
|
|
55
|
+
"@nestjs/core": "^11.1.12",
|
|
56
|
+
"@types/node": "^22.0.0",
|
|
57
|
+
"reflect-metadata": "^0.2.2",
|
|
58
|
+
"tsup": "^8.5.1",
|
|
59
|
+
"typescript": "^5.9.3"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {}
|
|
62
|
+
}
|