@xtrape/capsule-agent-node 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -0
- package/dist/index.cjs +50 -0
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +50 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -88,6 +88,38 @@ const svc = await serveCeService({
|
|
|
88
88
|
process.on("SIGTERM", () => void svc.close()); // stop heartbeat + deregister + close
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
+
### CE service actions
|
|
92
|
+
|
|
93
|
+
Pass `actions` to expose operator-triggerable operations over `GET /actions`,
|
|
94
|
+
`POST /actions/:name/prepare`, and `POST /actions/:name/execute`:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
const svc = await serveCeService({
|
|
98
|
+
// ...as above
|
|
99
|
+
actions: [
|
|
100
|
+
{
|
|
101
|
+
name: "relogin",
|
|
102
|
+
label: "Re-login account",
|
|
103
|
+
// Optional two-phase prepare: fetch live defaults/current state before
|
|
104
|
+
// the operator fills the form (advertised as requiresPrepare: true).
|
|
105
|
+
prepare: async () => ({ initialPayload: { accountId: "acc-1" } }),
|
|
106
|
+
execute: async (input) => ({ ok: true }),
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "full-sync",
|
|
110
|
+
label: "Full re-sync",
|
|
111
|
+
// Long-running action (server-ce#41 Stage B): when the pushed execute
|
|
112
|
+
// carries x-xtrape-command-id, the SDK replies 202 RUNNING immediately,
|
|
113
|
+
// runs execute in the background, and reports the outcome to server-ce
|
|
114
|
+
// with the per-instance access token. Against an older server-ce
|
|
115
|
+
// (no header) the action falls back to synchronous execution.
|
|
116
|
+
async: true,
|
|
117
|
+
execute: async (input) => runFullSync(input),
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
91
123
|
### Lower-level agent
|
|
92
124
|
|
|
93
125
|
When the service already runs its own HTTP server, use `CeServiceAgent` directly:
|
package/dist/index.cjs
CHANGED
|
@@ -546,6 +546,27 @@ var CeServiceAgent = class {
|
|
|
546
546
|
...this.lifecycleAuthHeaders() ? { headers: this.lifecycleAuthHeaders() } : {}
|
|
547
547
|
});
|
|
548
548
|
}
|
|
549
|
+
/**
|
|
550
|
+
* Report the outcome of an async action (server-ce#41 Stage B):
|
|
551
|
+
* POST /v1/instances/{instanceId}/commands/{commandId}/result using the
|
|
552
|
+
* per-instance access token. `outcome.result` carries the action payload for
|
|
553
|
+
* COMPLETED; errorCode/errorMessage describe a FAILED run.
|
|
554
|
+
*/
|
|
555
|
+
async reportCommandResult(commandId, outcome) {
|
|
556
|
+
const body = {
|
|
557
|
+
contractVersion: import_capsule_contracts_node.CE_CONTRACT_VERSION,
|
|
558
|
+
commandId,
|
|
559
|
+
status: outcome.status,
|
|
560
|
+
...outcome.result !== void 0 ? { result: outcome.result } : {},
|
|
561
|
+
...outcome.errorCode ? { errorCode: outcome.errorCode } : {},
|
|
562
|
+
...outcome.errorMessage ? { errorMessage: outcome.errorMessage } : {}
|
|
563
|
+
};
|
|
564
|
+
await this.post(
|
|
565
|
+
`/v1/instances/${encodeURIComponent(this.instanceId)}/commands/${encodeURIComponent(commandId)}/result`,
|
|
566
|
+
body,
|
|
567
|
+
this.lifecycleAuthHeaders()
|
|
568
|
+
);
|
|
569
|
+
}
|
|
549
570
|
/**
|
|
550
571
|
* Validate an inbound message, run the handler, and build the reply message
|
|
551
572
|
* (senderType=SERVICE, targeted back at the sender, with replyToMessageId).
|
|
@@ -601,6 +622,7 @@ var CeServiceAgent = class {
|
|
|
601
622
|
|
|
602
623
|
// src/ce/serve.ts
|
|
603
624
|
var import_node_http = require("http");
|
|
625
|
+
var import_capsule_contracts_node2 = require("@xtrape/capsule-contracts-node");
|
|
604
626
|
function send(res, status, body) {
|
|
605
627
|
res.writeHead(status, { "content-type": "application/json" });
|
|
606
628
|
res.end(JSON.stringify(body));
|
|
@@ -711,6 +733,7 @@ async function serveCeService(opts) {
|
|
|
711
733
|
category: action.category,
|
|
712
734
|
order: action.order,
|
|
713
735
|
requiresPrepare: typeof action.prepare === "function" ? true : void 0,
|
|
736
|
+
async: action.async === true ? true : void 0,
|
|
714
737
|
inputSchema: action.inputSchema
|
|
715
738
|
}))
|
|
716
739
|
);
|
|
@@ -769,6 +792,33 @@ async function serveCeService(opts) {
|
|
|
769
792
|
});
|
|
770
793
|
return;
|
|
771
794
|
}
|
|
795
|
+
const commandIdRaw = req.headers["x-xtrape-command-id"];
|
|
796
|
+
const commandId = Array.isArray(commandIdRaw) ? commandIdRaw[0] : commandIdRaw;
|
|
797
|
+
if (action.async === true && typeof commandId === "string" && commandId.length > 0 && agent) {
|
|
798
|
+
const reporter = agent;
|
|
799
|
+
send(res, 202, { contractVersion: import_capsule_contracts_node2.CE_CONTRACT_VERSION, status: "RUNNING", commandId });
|
|
800
|
+
void (async () => {
|
|
801
|
+
let outcome;
|
|
802
|
+
try {
|
|
803
|
+
const result = await action.execute(payload);
|
|
804
|
+
outcome = {
|
|
805
|
+
status: "COMPLETED",
|
|
806
|
+
...result !== void 0 && result !== null && typeof result === "object" && !Array.isArray(result) ? { result } : result !== void 0 ? { result: { value: result } } : {}
|
|
807
|
+
};
|
|
808
|
+
} catch (error) {
|
|
809
|
+
outcome = {
|
|
810
|
+
status: "FAILED",
|
|
811
|
+
errorCode: "ACTION_FAILED",
|
|
812
|
+
errorMessage: error instanceof Error ? error.message : "action handler failed"
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
try {
|
|
816
|
+
await reporter.reportCommandResult(commandId, outcome);
|
|
817
|
+
} catch {
|
|
818
|
+
}
|
|
819
|
+
})();
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
772
822
|
try {
|
|
773
823
|
send(res, 200, await action.execute(payload));
|
|
774
824
|
} catch (error) {
|
package/dist/index.d.cts
CHANGED
|
@@ -346,6 +346,18 @@ declare class CeServiceAgent {
|
|
|
346
346
|
stopHeartbeat(): void;
|
|
347
347
|
/** DELETE /v1/instances/{id} — graceful deregister. */
|
|
348
348
|
deregister(): Promise<void>;
|
|
349
|
+
/**
|
|
350
|
+
* Report the outcome of an async action (server-ce#41 Stage B):
|
|
351
|
+
* POST /v1/instances/{instanceId}/commands/{commandId}/result using the
|
|
352
|
+
* per-instance access token. `outcome.result` carries the action payload for
|
|
353
|
+
* COMPLETED; errorCode/errorMessage describe a FAILED run.
|
|
354
|
+
*/
|
|
355
|
+
reportCommandResult(commandId: string, outcome: {
|
|
356
|
+
status: "COMPLETED" | "FAILED";
|
|
357
|
+
result?: Record<string, unknown>;
|
|
358
|
+
errorCode?: string;
|
|
359
|
+
errorMessage?: string;
|
|
360
|
+
}): Promise<void>;
|
|
349
361
|
/**
|
|
350
362
|
* Validate an inbound message, run the handler, and build the reply message
|
|
351
363
|
* (senderType=SERVICE, targeted back at the sender, with replyToMessageId).
|
|
@@ -435,6 +447,15 @@ interface CeServiceActionDefinition {
|
|
|
435
447
|
* current state. Advertised as `requiresPrepare: true` in the action list.
|
|
436
448
|
*/
|
|
437
449
|
prepare?(input?: unknown): CeActionPrepareResult | Promise<CeActionPrepareResult>;
|
|
450
|
+
/**
|
|
451
|
+
* Long-running action (server-ce#41 Stage B). When true and the pushed
|
|
452
|
+
* execute carries an `x-xtrape-command-id` header, the listener replies
|
|
453
|
+
* 202 { status: "RUNNING", commandId } immediately, runs `execute` in the
|
|
454
|
+
* background, and reports the outcome via
|
|
455
|
+
* `CeServiceAgent.reportCommandResult`. Without the header (older server-ce)
|
|
456
|
+
* the action falls back to synchronous execution.
|
|
457
|
+
*/
|
|
458
|
+
async?: boolean;
|
|
438
459
|
execute(input: unknown): unknown | Promise<unknown>;
|
|
439
460
|
}
|
|
440
461
|
interface ServeCeServiceHandle {
|
package/dist/index.d.ts
CHANGED
|
@@ -346,6 +346,18 @@ declare class CeServiceAgent {
|
|
|
346
346
|
stopHeartbeat(): void;
|
|
347
347
|
/** DELETE /v1/instances/{id} — graceful deregister. */
|
|
348
348
|
deregister(): Promise<void>;
|
|
349
|
+
/**
|
|
350
|
+
* Report the outcome of an async action (server-ce#41 Stage B):
|
|
351
|
+
* POST /v1/instances/{instanceId}/commands/{commandId}/result using the
|
|
352
|
+
* per-instance access token. `outcome.result` carries the action payload for
|
|
353
|
+
* COMPLETED; errorCode/errorMessage describe a FAILED run.
|
|
354
|
+
*/
|
|
355
|
+
reportCommandResult(commandId: string, outcome: {
|
|
356
|
+
status: "COMPLETED" | "FAILED";
|
|
357
|
+
result?: Record<string, unknown>;
|
|
358
|
+
errorCode?: string;
|
|
359
|
+
errorMessage?: string;
|
|
360
|
+
}): Promise<void>;
|
|
349
361
|
/**
|
|
350
362
|
* Validate an inbound message, run the handler, and build the reply message
|
|
351
363
|
* (senderType=SERVICE, targeted back at the sender, with replyToMessageId).
|
|
@@ -435,6 +447,15 @@ interface CeServiceActionDefinition {
|
|
|
435
447
|
* current state. Advertised as `requiresPrepare: true` in the action list.
|
|
436
448
|
*/
|
|
437
449
|
prepare?(input?: unknown): CeActionPrepareResult | Promise<CeActionPrepareResult>;
|
|
450
|
+
/**
|
|
451
|
+
* Long-running action (server-ce#41 Stage B). When true and the pushed
|
|
452
|
+
* execute carries an `x-xtrape-command-id` header, the listener replies
|
|
453
|
+
* 202 { status: "RUNNING", commandId } immediately, runs `execute` in the
|
|
454
|
+
* background, and reports the outcome via
|
|
455
|
+
* `CeServiceAgent.reportCommandResult`. Without the header (older server-ce)
|
|
456
|
+
* the action falls back to synchronous execution.
|
|
457
|
+
*/
|
|
458
|
+
async?: boolean;
|
|
438
459
|
execute(input: unknown): unknown | Promise<unknown>;
|
|
439
460
|
}
|
|
440
461
|
interface ServeCeServiceHandle {
|
package/dist/index.js
CHANGED
|
@@ -512,6 +512,27 @@ var CeServiceAgent = class {
|
|
|
512
512
|
...this.lifecycleAuthHeaders() ? { headers: this.lifecycleAuthHeaders() } : {}
|
|
513
513
|
});
|
|
514
514
|
}
|
|
515
|
+
/**
|
|
516
|
+
* Report the outcome of an async action (server-ce#41 Stage B):
|
|
517
|
+
* POST /v1/instances/{instanceId}/commands/{commandId}/result using the
|
|
518
|
+
* per-instance access token. `outcome.result` carries the action payload for
|
|
519
|
+
* COMPLETED; errorCode/errorMessage describe a FAILED run.
|
|
520
|
+
*/
|
|
521
|
+
async reportCommandResult(commandId, outcome) {
|
|
522
|
+
const body = {
|
|
523
|
+
contractVersion: CE_CONTRACT_VERSION,
|
|
524
|
+
commandId,
|
|
525
|
+
status: outcome.status,
|
|
526
|
+
...outcome.result !== void 0 ? { result: outcome.result } : {},
|
|
527
|
+
...outcome.errorCode ? { errorCode: outcome.errorCode } : {},
|
|
528
|
+
...outcome.errorMessage ? { errorMessage: outcome.errorMessage } : {}
|
|
529
|
+
};
|
|
530
|
+
await this.post(
|
|
531
|
+
`/v1/instances/${encodeURIComponent(this.instanceId)}/commands/${encodeURIComponent(commandId)}/result`,
|
|
532
|
+
body,
|
|
533
|
+
this.lifecycleAuthHeaders()
|
|
534
|
+
);
|
|
535
|
+
}
|
|
515
536
|
/**
|
|
516
537
|
* Validate an inbound message, run the handler, and build the reply message
|
|
517
538
|
* (senderType=SERVICE, targeted back at the sender, with replyToMessageId).
|
|
@@ -567,6 +588,7 @@ var CeServiceAgent = class {
|
|
|
567
588
|
|
|
568
589
|
// src/ce/serve.ts
|
|
569
590
|
import { createServer } from "http";
|
|
591
|
+
import { CE_CONTRACT_VERSION as CE_CONTRACT_VERSION2 } from "@xtrape/capsule-contracts-node";
|
|
570
592
|
function send(res, status, body) {
|
|
571
593
|
res.writeHead(status, { "content-type": "application/json" });
|
|
572
594
|
res.end(JSON.stringify(body));
|
|
@@ -677,6 +699,7 @@ async function serveCeService(opts) {
|
|
|
677
699
|
category: action.category,
|
|
678
700
|
order: action.order,
|
|
679
701
|
requiresPrepare: typeof action.prepare === "function" ? true : void 0,
|
|
702
|
+
async: action.async === true ? true : void 0,
|
|
680
703
|
inputSchema: action.inputSchema
|
|
681
704
|
}))
|
|
682
705
|
);
|
|
@@ -735,6 +758,33 @@ async function serveCeService(opts) {
|
|
|
735
758
|
});
|
|
736
759
|
return;
|
|
737
760
|
}
|
|
761
|
+
const commandIdRaw = req.headers["x-xtrape-command-id"];
|
|
762
|
+
const commandId = Array.isArray(commandIdRaw) ? commandIdRaw[0] : commandIdRaw;
|
|
763
|
+
if (action.async === true && typeof commandId === "string" && commandId.length > 0 && agent) {
|
|
764
|
+
const reporter = agent;
|
|
765
|
+
send(res, 202, { contractVersion: CE_CONTRACT_VERSION2, status: "RUNNING", commandId });
|
|
766
|
+
void (async () => {
|
|
767
|
+
let outcome;
|
|
768
|
+
try {
|
|
769
|
+
const result = await action.execute(payload);
|
|
770
|
+
outcome = {
|
|
771
|
+
status: "COMPLETED",
|
|
772
|
+
...result !== void 0 && result !== null && typeof result === "object" && !Array.isArray(result) ? { result } : result !== void 0 ? { result: { value: result } } : {}
|
|
773
|
+
};
|
|
774
|
+
} catch (error) {
|
|
775
|
+
outcome = {
|
|
776
|
+
status: "FAILED",
|
|
777
|
+
errorCode: "ACTION_FAILED",
|
|
778
|
+
errorMessage: error instanceof Error ? error.message : "action handler failed"
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
try {
|
|
782
|
+
await reporter.reportCommandResult(commandId, outcome);
|
|
783
|
+
} catch {
|
|
784
|
+
}
|
|
785
|
+
})();
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
738
788
|
try {
|
|
739
789
|
send(res, 200, await action.execute(payload));
|
|
740
790
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xtrape/capsule-agent-node",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Node.js Embedded Agent SDK for reporting Workers to the Xtrape control plane.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"xtrape",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"lint": "tsc --noEmit"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@xtrape/capsule-contracts-node": "^0.6.
|
|
47
|
+
"@xtrape/capsule-contracts-node": "^0.6.1"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"tsup": "^8.3.5",
|