@tellzm/n8n-nodes 0.1.0-beta.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/README.md +32 -0
- package/dist/credentials/AxonOAuth2Api.credentials.d.ts +9 -0
- package/dist/credentials/AxonOAuth2Api.credentials.js +78 -0
- package/dist/nodes/Axon/Axon.node.d.ts +5 -0
- package/dist/nodes/Axon/Axon.node.js +101 -0
- package/dist/nodes/Axon/descriptions/_index.d.ts +2 -0
- package/dist/nodes/Axon/descriptions/_index.js +32 -0
- package/dist/nodes/Axon/descriptions/_shared.d.ts +5 -0
- package/dist/nodes/Axon/descriptions/_shared.js +30 -0
- package/dist/nodes/Axon/descriptions/calendar.d.ts +19 -0
- package/dist/nodes/Axon/descriptions/calendar.js +120 -0
- package/dist/nodes/Axon/descriptions/data-tables.d.ts +23 -0
- package/dist/nodes/Axon/descriptions/data-tables.js +154 -0
- package/dist/nodes/Axon/descriptions/form-process.d.ts +23 -0
- package/dist/nodes/Axon/descriptions/form-process.js +165 -0
- package/dist/nodes/Axon/descriptions/forms.d.ts +15 -0
- package/dist/nodes/Axon/descriptions/forms.js +86 -0
- package/dist/nodes/Axon/descriptions/pages.d.ts +19 -0
- package/dist/nodes/Axon/descriptions/pages.js +120 -0
- package/dist/nodes/Axon/descriptions/projects.d.ts +15 -0
- package/dist/nodes/Axon/descriptions/projects.js +77 -0
- package/dist/nodes/Axon/descriptions/raw.d.ts +7 -0
- package/dist/nodes/Axon/descriptions/raw.js +70 -0
- package/dist/nodes/Axon/descriptions/routines.d.ts +15 -0
- package/dist/nodes/Axon/descriptions/routines.js +105 -0
- package/dist/nodes/Axon/descriptions/tasks.d.ts +19 -0
- package/dist/nodes/Axon/descriptions/tasks.js +144 -0
- package/dist/nodes/Axon/helpers/apiRequest.d.ts +10 -0
- package/dist/nodes/Axon/helpers/apiRequest.js +86 -0
- package/dist/nodes/Axon/helpers/idempotency.d.ts +2 -0
- package/dist/nodes/Axon/helpers/idempotency.js +15 -0
- package/dist/nodes/Axon/helpers/paginate.d.ts +2 -0
- package/dist/nodes/Axon/helpers/paginate.js +26 -0
- package/dist/nodes/Axon/helpers/scopeGuard.d.ts +12 -0
- package/dist/nodes/Axon/helpers/scopeGuard.js +24 -0
- package/dist/nodes/AxonTrigger/AxonTrigger.node.d.ts +12 -0
- package/dist/nodes/AxonTrigger/AxonTrigger.node.js +131 -0
- package/dist/nodes/AxonTrigger/eventTypes.d.ts +2 -0
- package/dist/nodes/AxonTrigger/eventTypes.js +22 -0
- package/dist/nodes/AxonTrigger/helpers/signatureVerify.d.ts +1 -0
- package/dist/nodes/AxonTrigger/helpers/signatureVerify.js +20 -0
- package/package.json +56 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handlers = exports.properties = void 0;
|
|
4
|
+
const apiRequest_js_1 = require("../helpers/apiRequest.js");
|
|
5
|
+
const paginate_js_1 = require("../helpers/paginate.js");
|
|
6
|
+
const scopeGuard_js_1 = require("../helpers/scopeGuard.js");
|
|
7
|
+
const show = (ops) => ({
|
|
8
|
+
show: { resource: ["routine"], ...(ops ? { operation: ops } : {}) },
|
|
9
|
+
});
|
|
10
|
+
exports.properties = [
|
|
11
|
+
{
|
|
12
|
+
displayName: "Operation",
|
|
13
|
+
name: "operation",
|
|
14
|
+
type: "options",
|
|
15
|
+
noDataExpression: true,
|
|
16
|
+
displayOptions: show(),
|
|
17
|
+
default: "list",
|
|
18
|
+
options: [
|
|
19
|
+
{ name: "Get", value: "get", action: "Get a routine" },
|
|
20
|
+
{ name: "List", value: "list", action: "List routines" },
|
|
21
|
+
{ name: "List Runs", value: "listRuns", action: "List runs" },
|
|
22
|
+
{ name: "Submit Task Value", value: "submitTaskValue", action: "Submit a routine task value" },
|
|
23
|
+
{ name: "Update Routine Task", value: "updateRoutineTask", action: "Update a routine task" },
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
displayName: "Routine ID",
|
|
28
|
+
name: "routineId",
|
|
29
|
+
type: "string",
|
|
30
|
+
required: true,
|
|
31
|
+
default: "",
|
|
32
|
+
description: "The id of the routine",
|
|
33
|
+
displayOptions: show(["get", "listRuns", "submitTaskValue", "updateRoutineTask"]),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
displayName: "Run ID",
|
|
37
|
+
name: "runId",
|
|
38
|
+
type: "string",
|
|
39
|
+
required: true,
|
|
40
|
+
default: "",
|
|
41
|
+
description: "The id of the routine run",
|
|
42
|
+
displayOptions: show(["submitTaskValue"]),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
displayName: "Task ID",
|
|
46
|
+
name: "routineTaskId",
|
|
47
|
+
type: "string",
|
|
48
|
+
required: true,
|
|
49
|
+
default: "",
|
|
50
|
+
description: "The id of the routine task",
|
|
51
|
+
displayOptions: show(["submitTaskValue", "updateRoutineTask"]),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
displayName: "Task Value (JSON)",
|
|
55
|
+
name: "taskValue",
|
|
56
|
+
type: "json",
|
|
57
|
+
default: "{}",
|
|
58
|
+
required: true,
|
|
59
|
+
description: "The value submitted for the routine task",
|
|
60
|
+
displayOptions: show(["submitTaskValue"]),
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
displayName: "Routine Task Body (JSON)",
|
|
64
|
+
name: "routineTaskBody",
|
|
65
|
+
type: "json",
|
|
66
|
+
default: "{}",
|
|
67
|
+
required: true,
|
|
68
|
+
description: "Routine task fields to update",
|
|
69
|
+
displayOptions: show(["updateRoutineTask"]),
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
async function get(i) {
|
|
73
|
+
await scopeGuard_js_1.assertScope.call(this, "routines.read");
|
|
74
|
+
const id = this.getNodeParameter("routineId", i);
|
|
75
|
+
const res = (await apiRequest_js_1.apiRequest.call(this, "GET", `/routines/${id}`));
|
|
76
|
+
return [{ json: (res.data ?? res) }];
|
|
77
|
+
}
|
|
78
|
+
async function list() {
|
|
79
|
+
await scopeGuard_js_1.assertScope.call(this, "routines.read");
|
|
80
|
+
const items = await (0, paginate_js_1.paginate)(this, "/routines");
|
|
81
|
+
return items.map((r) => ({ json: r }));
|
|
82
|
+
}
|
|
83
|
+
async function listRuns(i) {
|
|
84
|
+
await scopeGuard_js_1.assertScope.call(this, "routines.read");
|
|
85
|
+
const id = this.getNodeParameter("routineId", i);
|
|
86
|
+
const items = await (0, paginate_js_1.paginate)(this, `/routines/${id}/runs`);
|
|
87
|
+
return items.map((r) => ({ json: r }));
|
|
88
|
+
}
|
|
89
|
+
async function submitTaskValue(i) {
|
|
90
|
+
await scopeGuard_js_1.assertScope.call(this, "routines.write");
|
|
91
|
+
const runId = this.getNodeParameter("runId", i);
|
|
92
|
+
const taskId = this.getNodeParameter("routineTaskId", i);
|
|
93
|
+
const value = this.getNodeParameter("taskValue", i);
|
|
94
|
+
const res = (await apiRequest_js_1.apiRequest.call(this, "POST", `/routine-runs/${runId}/tasks/${taskId}/value`, { body: { value } }));
|
|
95
|
+
return [{ json: (res.data ?? res) }];
|
|
96
|
+
}
|
|
97
|
+
async function updateRoutineTask(i) {
|
|
98
|
+
await scopeGuard_js_1.assertScope.call(this, "routines.write");
|
|
99
|
+
const routineId = this.getNodeParameter("routineId", i);
|
|
100
|
+
const taskId = this.getNodeParameter("routineTaskId", i);
|
|
101
|
+
const body = this.getNodeParameter("routineTaskBody", i);
|
|
102
|
+
const res = (await apiRequest_js_1.apiRequest.call(this, "PATCH", `/routines/${routineId}/tasks/${taskId}`, { body }));
|
|
103
|
+
return [{ json: (res.data ?? res) }];
|
|
104
|
+
}
|
|
105
|
+
exports.handlers = { get, list, listRuns, submitTaskValue, updateRoutineTask };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from "n8n-workflow";
|
|
2
|
+
export declare const properties: INodeProperties[];
|
|
3
|
+
declare function get(this: IExecuteFunctions, i: number): Promise<INodeExecutionData[]>;
|
|
4
|
+
declare function list(this: IExecuteFunctions, i: number): Promise<INodeExecutionData[]>;
|
|
5
|
+
declare function search(this: IExecuteFunctions, i: number): Promise<INodeExecutionData[]>;
|
|
6
|
+
declare function create(this: IExecuteFunctions, i: number): Promise<INodeExecutionData[]>;
|
|
7
|
+
declare function update(this: IExecuteFunctions, i: number): Promise<INodeExecutionData[]>;
|
|
8
|
+
declare function del(this: IExecuteFunctions, i: number): Promise<INodeExecutionData[]>;
|
|
9
|
+
declare function complete(this: IExecuteFunctions, i: number): Promise<INodeExecutionData[]>;
|
|
10
|
+
export declare const handlers: {
|
|
11
|
+
get: typeof get;
|
|
12
|
+
list: typeof list;
|
|
13
|
+
search: typeof search;
|
|
14
|
+
create: typeof create;
|
|
15
|
+
update: typeof update;
|
|
16
|
+
delete: typeof del;
|
|
17
|
+
complete: typeof complete;
|
|
18
|
+
};
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handlers = exports.properties = void 0;
|
|
4
|
+
const apiRequest_js_1 = require("../helpers/apiRequest.js");
|
|
5
|
+
const paginate_js_1 = require("../helpers/paginate.js");
|
|
6
|
+
const scopeGuard_js_1 = require("../helpers/scopeGuard.js");
|
|
7
|
+
const idempotency_js_1 = require("../helpers/idempotency.js");
|
|
8
|
+
const showOn = (resource, ops) => ({
|
|
9
|
+
show: {
|
|
10
|
+
resource: [resource],
|
|
11
|
+
...(ops ? { operation: ops } : {}),
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
exports.properties = [
|
|
15
|
+
{
|
|
16
|
+
displayName: "Operation",
|
|
17
|
+
name: "operation",
|
|
18
|
+
type: "options",
|
|
19
|
+
noDataExpression: true,
|
|
20
|
+
displayOptions: showOn("task"),
|
|
21
|
+
default: "list",
|
|
22
|
+
options: [
|
|
23
|
+
{ name: "Get", value: "get", description: "Get one task by id", action: "Get a task" },
|
|
24
|
+
{ name: "List", value: "list", description: "List tasks with filters; paginates transparently", action: "List tasks" },
|
|
25
|
+
{ name: "Search", value: "search", description: "Full-text search across tasks", action: "Search tasks" },
|
|
26
|
+
{ name: "Create", value: "create", description: "Create a new task", action: "Create a task" },
|
|
27
|
+
{ name: "Update", value: "update", description: "Update an existing task", action: "Update a task" },
|
|
28
|
+
{ name: "Delete", value: "delete", description: "Soft-delete a task", action: "Delete a task" },
|
|
29
|
+
{ name: "Complete", value: "complete", description: "Mark a task as done", action: "Complete a task" },
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
displayName: "Task ID",
|
|
34
|
+
name: "taskId",
|
|
35
|
+
type: "string",
|
|
36
|
+
required: true,
|
|
37
|
+
default: "",
|
|
38
|
+
description: "The id of the task",
|
|
39
|
+
displayOptions: showOn("task", ["get", "update", "delete", "complete"]),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
displayName: "Project ID",
|
|
43
|
+
name: "projectId",
|
|
44
|
+
type: "string",
|
|
45
|
+
default: "",
|
|
46
|
+
description: "Filter to tasks in this project (optional)",
|
|
47
|
+
displayOptions: showOn("task", ["list"]),
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
displayName: "Status",
|
|
51
|
+
name: "status",
|
|
52
|
+
type: "options",
|
|
53
|
+
default: "",
|
|
54
|
+
description: "Filter to tasks with this status",
|
|
55
|
+
options: [
|
|
56
|
+
{ name: "(Any)", value: "" },
|
|
57
|
+
{ name: "Todo", value: "todo" },
|
|
58
|
+
{ name: "In Progress", value: "in_progress" },
|
|
59
|
+
{ name: "Done", value: "done" },
|
|
60
|
+
{ name: "Blocked", value: "blocked" },
|
|
61
|
+
],
|
|
62
|
+
displayOptions: showOn("task", ["list"]),
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
displayName: "Assignee ID",
|
|
66
|
+
name: "assigneeId",
|
|
67
|
+
type: "string",
|
|
68
|
+
default: "",
|
|
69
|
+
description: "Filter to tasks assigned to this user (optional)",
|
|
70
|
+
displayOptions: showOn("task", ["list"]),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
displayName: "Search Query",
|
|
74
|
+
name: "q",
|
|
75
|
+
type: "string",
|
|
76
|
+
required: true,
|
|
77
|
+
default: "",
|
|
78
|
+
description: "Text to search for in task titles + descriptions",
|
|
79
|
+
displayOptions: showOn("task", ["search"]),
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
displayName: "Task Body (JSON)",
|
|
83
|
+
name: "taskBody",
|
|
84
|
+
type: "json",
|
|
85
|
+
default: '{\n "title": "",\n "projectId": ""\n}',
|
|
86
|
+
required: true,
|
|
87
|
+
description: "Task fields. For create: title and projectId are required; description, assigneeId, dueDate, priority, status, tags[] are optional.",
|
|
88
|
+
displayOptions: showOn("task", ["create", "update"]),
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
async function get(i) {
|
|
92
|
+
await scopeGuard_js_1.assertScope.call(this, "tasks.read");
|
|
93
|
+
const id = this.getNodeParameter("taskId", i);
|
|
94
|
+
const res = (await apiRequest_js_1.apiRequest.call(this, "GET", `/tasks/${id}`));
|
|
95
|
+
return [{ json: (res.data ?? res) }];
|
|
96
|
+
}
|
|
97
|
+
async function list(i) {
|
|
98
|
+
await scopeGuard_js_1.assertScope.call(this, "tasks.read");
|
|
99
|
+
const projectId = this.getNodeParameter("projectId", i, "");
|
|
100
|
+
const status = this.getNodeParameter("status", i, "");
|
|
101
|
+
const assigneeId = this.getNodeParameter("assigneeId", i, "");
|
|
102
|
+
const qs = {};
|
|
103
|
+
if (projectId)
|
|
104
|
+
qs.projectId = projectId;
|
|
105
|
+
if (status)
|
|
106
|
+
qs.status = status;
|
|
107
|
+
if (assigneeId)
|
|
108
|
+
qs.assigneeId = assigneeId;
|
|
109
|
+
const items = await (0, paginate_js_1.paginate)(this, "/tasks", qs);
|
|
110
|
+
return items.map((t) => ({ json: t }));
|
|
111
|
+
}
|
|
112
|
+
async function search(i) {
|
|
113
|
+
await scopeGuard_js_1.assertScope.call(this, "tasks.read");
|
|
114
|
+
const q = this.getNodeParameter("q", i);
|
|
115
|
+
const items = await (0, paginate_js_1.paginate)(this, "/tasks/search", { q });
|
|
116
|
+
return items.map((t) => ({ json: t }));
|
|
117
|
+
}
|
|
118
|
+
async function create(i) {
|
|
119
|
+
await scopeGuard_js_1.assertScope.call(this, "tasks.write");
|
|
120
|
+
const body = this.getNodeParameter("taskBody", i);
|
|
121
|
+
const key = (0, idempotency_js_1.deriveIdempotencyKey)(this, i, "task.create");
|
|
122
|
+
const res = (await apiRequest_js_1.apiRequest.call(this, "POST", "/tasks", { body, idempotencyKey: key }));
|
|
123
|
+
return [{ json: (res.data ?? res) }];
|
|
124
|
+
}
|
|
125
|
+
async function update(i) {
|
|
126
|
+
await scopeGuard_js_1.assertScope.call(this, "tasks.write");
|
|
127
|
+
const id = this.getNodeParameter("taskId", i);
|
|
128
|
+
const body = this.getNodeParameter("taskBody", i);
|
|
129
|
+
const res = (await apiRequest_js_1.apiRequest.call(this, "PATCH", `/tasks/${id}`, { body }));
|
|
130
|
+
return [{ json: (res.data ?? res) }];
|
|
131
|
+
}
|
|
132
|
+
async function del(i) {
|
|
133
|
+
await scopeGuard_js_1.assertScope.call(this, "tasks.write");
|
|
134
|
+
const id = this.getNodeParameter("taskId", i);
|
|
135
|
+
await apiRequest_js_1.apiRequest.call(this, "DELETE", `/tasks/${id}`);
|
|
136
|
+
return [{ json: { ok: true, id, deleted: true } }];
|
|
137
|
+
}
|
|
138
|
+
async function complete(i) {
|
|
139
|
+
await scopeGuard_js_1.assertScope.call(this, "tasks.write");
|
|
140
|
+
const id = this.getNodeParameter("taskId", i);
|
|
141
|
+
const res = (await apiRequest_js_1.apiRequest.call(this, "PATCH", `/tasks/${id}`, { body: { status: "done" } }));
|
|
142
|
+
return [{ json: (res.data ?? res) }];
|
|
143
|
+
}
|
|
144
|
+
exports.handlers = { get, list, search, create, update, "delete": del, complete };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { IExecuteFunctions, IHookFunctions, ILoadOptionsFunctions, IHttpRequestMethods, JsonObject } from "n8n-workflow";
|
|
2
|
+
type Ctx = IExecuteFunctions | IHookFunctions | ILoadOptionsFunctions;
|
|
3
|
+
export type AxonRequestOptions = {
|
|
4
|
+
qs?: Record<string, unknown>;
|
|
5
|
+
body?: unknown;
|
|
6
|
+
idempotencyKey?: string;
|
|
7
|
+
headers?: Record<string, string>;
|
|
8
|
+
};
|
|
9
|
+
export declare function apiRequest(this: Ctx, method: IHttpRequestMethods, path: string, options?: AxonRequestOptions): Promise<JsonObject | JsonObject[]>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Centralized request helper for every Axon action node. Wraps n8n's
|
|
3
|
+
// built-in OAuth2 request helper with three platform-specific niceties:
|
|
4
|
+
//
|
|
5
|
+
// 1. Auto-refresh: n8n's requestOAuth2 already refreshes on 401, but we
|
|
6
|
+
// surface a clean "credential revoked" error if the refresh itself
|
|
7
|
+
// fails (workspace admin revoked the connection).
|
|
8
|
+
// 2. Retry-After: on 429/503 with Retry-After, sleep and retry ONCE.
|
|
9
|
+
// The platform's per-credential limiter (T013) issues these.
|
|
10
|
+
// 3. Idempotency-Key: if the caller passes one, forward as a header.
|
|
11
|
+
//
|
|
12
|
+
// Surface-area parity with n8n's `IExecuteFunctions.helpers.requestOAuth2`,
|
|
13
|
+
// but with our extra behavior layered on top.
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.apiRequest = apiRequest;
|
|
16
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
17
|
+
const DEFAULT_BASE = "https://api.tellzm.com";
|
|
18
|
+
function sleep(ms) {
|
|
19
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
20
|
+
}
|
|
21
|
+
async function apiRequest(method, path, options = {}) {
|
|
22
|
+
const base = (process.env["AXON_API_BASE"] ?? DEFAULT_BASE).replace(/\/$/, "");
|
|
23
|
+
const url = path.startsWith("http") ? path : `${base}${path.startsWith("/") ? path : "/" + path}`;
|
|
24
|
+
const headers = {
|
|
25
|
+
"content-type": "application/json",
|
|
26
|
+
...(options.headers ?? {}),
|
|
27
|
+
};
|
|
28
|
+
if (options.idempotencyKey)
|
|
29
|
+
headers["idempotency-key"] = options.idempotencyKey;
|
|
30
|
+
const opts = {
|
|
31
|
+
method,
|
|
32
|
+
url,
|
|
33
|
+
headers,
|
|
34
|
+
qs: options.qs ?? undefined,
|
|
35
|
+
body: options.body,
|
|
36
|
+
json: true,
|
|
37
|
+
returnFullResponse: true,
|
|
38
|
+
};
|
|
39
|
+
async function doRequest() {
|
|
40
|
+
return this.helpers.requestOAuth2.call(this, "axonOAuth2Api", opts);
|
|
41
|
+
}
|
|
42
|
+
let response;
|
|
43
|
+
try {
|
|
44
|
+
response = (await doRequest.call(this));
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
// n8n's helper throws on non-2xx with .response populated.
|
|
48
|
+
const errAny = err;
|
|
49
|
+
const status = errAny.statusCode ?? errAny.response?.status;
|
|
50
|
+
if (status === 401) {
|
|
51
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), "Axon credential is invalid or has been revoked. Re-create the credential to continue.", { description: errAny.message ?? "" });
|
|
52
|
+
}
|
|
53
|
+
if (status === 429 || status === 503) {
|
|
54
|
+
// Retry once after Retry-After.
|
|
55
|
+
const retryAfter = parseRetryAfter(errAny.response?.body) ?? 5;
|
|
56
|
+
await sleep(Math.min(retryAfter * 1000, 60000));
|
|
57
|
+
try {
|
|
58
|
+
response = (await doRequest.call(this));
|
|
59
|
+
}
|
|
60
|
+
catch (err2) {
|
|
61
|
+
throw new n8n_workflow_1.NodeApiError(this.getNode(), err2, {
|
|
62
|
+
message: `Axon API still rate-limited after Retry-After (${retryAfter}s).`,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
throw new n8n_workflow_1.NodeApiError(this.getNode(), errAny);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Hard-cap on 4xx/5xx that slipped through the helper (defensive).
|
|
71
|
+
if (response.statusCode >= 400) {
|
|
72
|
+
throw new n8n_workflow_1.NodeApiError(this.getNode(), { statusCode: response.statusCode, body: response.body });
|
|
73
|
+
}
|
|
74
|
+
return response.body;
|
|
75
|
+
}
|
|
76
|
+
function parseRetryAfter(body) {
|
|
77
|
+
// The API includes Retry-After in the response header, but n8n's helper
|
|
78
|
+
// already follows it. This fallback inspects the body for a hint just in
|
|
79
|
+
// case the receiver passed it through.
|
|
80
|
+
if (body && typeof body === "object" && "retryAfter" in body) {
|
|
81
|
+
const v = body.retryAfter;
|
|
82
|
+
if (typeof v === "number")
|
|
83
|
+
return v;
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Deterministic idempotency key derived from the n8n execution context.
|
|
3
|
+
// When n8n retries a workflow execution (e.g. on a transient failure), the
|
|
4
|
+
// same key is produced so the platform's MCP idempotency cache returns the
|
|
5
|
+
// original result instead of re-executing.
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.deriveIdempotencyKey = deriveIdempotencyKey;
|
|
8
|
+
const node_crypto_1 = require("node:crypto");
|
|
9
|
+
function deriveIdempotencyKey(ctx, itemIndex, operation) {
|
|
10
|
+
const workflow = ctx.getWorkflow();
|
|
11
|
+
const node = ctx.getNode();
|
|
12
|
+
const executionId = ctx.getExecutionId();
|
|
13
|
+
const seed = `${workflow.id ?? "wf"}|${executionId}|${node.name}|${operation}|${itemIndex}`;
|
|
14
|
+
return (0, node_crypto_1.createHash)("sha256").update(seed).digest("hex").slice(0, 32);
|
|
15
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Cursor-based pagination iterator for /list operations.
|
|
3
|
+
// Walks `nextCursor` until the API stops returning one, yielding each page's
|
|
4
|
+
// items. Returns the full array — n8n's per-item memory is the user's
|
|
5
|
+
// constraint, not ours.
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.paginate = paginate;
|
|
8
|
+
const apiRequest_js_1 = require("./apiRequest.js");
|
|
9
|
+
const MAX_PAGES = 500; // safety cap so a buggy server can't loop us forever
|
|
10
|
+
async function paginate(ctx, path, baseQs = {}) {
|
|
11
|
+
const all = [];
|
|
12
|
+
let cursor;
|
|
13
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
14
|
+
const qs = { ...baseQs };
|
|
15
|
+
if (cursor)
|
|
16
|
+
qs.cursor = cursor;
|
|
17
|
+
const res = (await apiRequest_js_1.apiRequest.call(ctx, "GET", path, { qs }));
|
|
18
|
+
const envelope = "data" in res ? res.data : res;
|
|
19
|
+
all.push(...envelope.items);
|
|
20
|
+
cursor = envelope.nextCursor;
|
|
21
|
+
if (!cursor)
|
|
22
|
+
return all;
|
|
23
|
+
}
|
|
24
|
+
// Hit the cap — return what we have and let the workflow see a warning.
|
|
25
|
+
return all;
|
|
26
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IExecuteFunctions, IHookFunctions, ILoadOptionsFunctions } from "n8n-workflow";
|
|
2
|
+
type Ctx = IExecuteFunctions | IHookFunctions | ILoadOptionsFunctions;
|
|
3
|
+
/**
|
|
4
|
+
* Read the granted scope set from the active credential and throw if the
|
|
5
|
+
* required scope isn't there. Called by every action handler before its
|
|
6
|
+
* apiRequest invocation.
|
|
7
|
+
*
|
|
8
|
+
* Scopes are stored as a space-separated string on the credential (n8n's
|
|
9
|
+
* default oauth2Api format), so we tokenize before checking.
|
|
10
|
+
*/
|
|
11
|
+
export declare function assertScope(this: Ctx, required: string): Promise<void>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Fail-fast scope check. Avoids hitting the API at all when the credential
|
|
3
|
+
// doesn't carry the required scope — the user gets an actionable n8n error
|
|
4
|
+
// before the network round-trip.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.assertScope = assertScope;
|
|
7
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
8
|
+
/**
|
|
9
|
+
* Read the granted scope set from the active credential and throw if the
|
|
10
|
+
* required scope isn't there. Called by every action handler before its
|
|
11
|
+
* apiRequest invocation.
|
|
12
|
+
*
|
|
13
|
+
* Scopes are stored as a space-separated string on the credential (n8n's
|
|
14
|
+
* default oauth2Api format), so we tokenize before checking.
|
|
15
|
+
*/
|
|
16
|
+
async function assertScope(required) {
|
|
17
|
+
const creds = (await this.getCredentials("axonOAuth2Api"));
|
|
18
|
+
const granted = new Set((creds.scope ?? "").split(/\s+/).filter(Boolean));
|
|
19
|
+
if (!granted.has(required)) {
|
|
20
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `This operation requires the "${required}" scope, but the credential doesn't have it.`, {
|
|
21
|
+
description: "Edit the Axon OAuth2 API credential, add the missing scope to the Scope field, and reconnect.",
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IHookFunctions, INodeType, INodeTypeDescription, IWebhookFunctions, IWebhookResponseData } from "n8n-workflow";
|
|
2
|
+
export declare class AxonTrigger implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
webhookMethods: {
|
|
5
|
+
default: {
|
|
6
|
+
create(this: IHookFunctions): Promise<boolean>;
|
|
7
|
+
delete(this: IHookFunctions): Promise<boolean>;
|
|
8
|
+
checkExists(this: IHookFunctions): Promise<boolean>;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
webhook(this: IWebhookFunctions): Promise<IWebhookResponseData>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// AxonTrigger node. Registers itself as a webhook subscriber on workflow
|
|
3
|
+
// activate, receives deliveries via n8n's webhook endpoint, verifies HMAC,
|
|
4
|
+
// and emits one n8n execution per delivery.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AxonTrigger = void 0;
|
|
7
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
8
|
+
const eventTypes_js_1 = require("./eventTypes.js");
|
|
9
|
+
const signatureVerify_js_1 = require("./helpers/signatureVerify.js");
|
|
10
|
+
const DEFAULT_BASE = "https://api.tellzm.com";
|
|
11
|
+
class AxonTrigger {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.description = {
|
|
14
|
+
displayName: "Axon Trigger",
|
|
15
|
+
name: "axonTrigger",
|
|
16
|
+
icon: "file:axon.svg",
|
|
17
|
+
group: ["trigger"],
|
|
18
|
+
version: 1,
|
|
19
|
+
description: "Fire workflows on Axon events (tasks, forms, processes, …).",
|
|
20
|
+
defaults: { name: "Axon Trigger" },
|
|
21
|
+
inputs: [],
|
|
22
|
+
outputs: ["main"],
|
|
23
|
+
credentials: [{ name: "axonOAuth2Api", required: true }],
|
|
24
|
+
webhooks: [
|
|
25
|
+
{
|
|
26
|
+
name: "default",
|
|
27
|
+
httpMethod: "POST",
|
|
28
|
+
responseMode: "onReceived",
|
|
29
|
+
path: "axon",
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
properties: [
|
|
33
|
+
{
|
|
34
|
+
displayName: "Event",
|
|
35
|
+
name: "eventType",
|
|
36
|
+
type: "options",
|
|
37
|
+
default: "task.created",
|
|
38
|
+
required: true,
|
|
39
|
+
description: "Which Axon event to subscribe to",
|
|
40
|
+
options: eventTypes_js_1.KNOWN_TRIGGER_EVENT_TYPES.map((e) => ({ name: e, value: e })),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
displayName: "Filters (JSON)",
|
|
44
|
+
name: "filtersJson",
|
|
45
|
+
type: "json",
|
|
46
|
+
default: "",
|
|
47
|
+
description: 'Optional filter tree. JSON with shape: { combinator: "AND" | "OR", conditions: [...] } or a single leaf { field, op, value }. Leave blank to deliver every event of this type. See docs.tellzm.com/n8n for the filter syntax.',
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
this.webhookMethods = {
|
|
52
|
+
default: {
|
|
53
|
+
async create() {
|
|
54
|
+
const webhookUrl = this.getNodeWebhookUrl("default");
|
|
55
|
+
const eventType = this.getNodeParameter("eventType");
|
|
56
|
+
const filtersRaw = this.getNodeParameter("filtersJson", "");
|
|
57
|
+
let filters = null;
|
|
58
|
+
const trimmed = (filtersRaw ?? "").trim();
|
|
59
|
+
if (trimmed) {
|
|
60
|
+
try {
|
|
61
|
+
filters = JSON.parse(trimmed);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Filters must be valid JSON. Got: ${trimmed.slice(0, 80)}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const base = (process.env["AXON_API_BASE"] ?? DEFAULT_BASE).replace(/\/$/, "");
|
|
68
|
+
const res = (await this.helpers.requestOAuth2.call(this, "axonOAuth2Api", {
|
|
69
|
+
method: "POST",
|
|
70
|
+
url: `${base}/webhook-subscriptions`,
|
|
71
|
+
headers: { "content-type": "application/json" },
|
|
72
|
+
body: { url: webhookUrl, eventType, filters },
|
|
73
|
+
json: true,
|
|
74
|
+
}));
|
|
75
|
+
const dto = "data" in res ? res.data : res;
|
|
76
|
+
const wd = this.getWorkflowStaticData("node");
|
|
77
|
+
wd.endpointId = dto.endpointId;
|
|
78
|
+
wd.secret = dto.secret;
|
|
79
|
+
return true;
|
|
80
|
+
},
|
|
81
|
+
async delete() {
|
|
82
|
+
const wd = this.getWorkflowStaticData("node");
|
|
83
|
+
const endpointId = wd.endpointId;
|
|
84
|
+
if (!endpointId)
|
|
85
|
+
return true;
|
|
86
|
+
const base = (process.env["AXON_API_BASE"] ?? DEFAULT_BASE).replace(/\/$/, "");
|
|
87
|
+
try {
|
|
88
|
+
await this.helpers.requestOAuth2.call(this, "axonOAuth2Api", {
|
|
89
|
+
method: "DELETE",
|
|
90
|
+
url: `${base}/webhook-subscriptions/${endpointId}`,
|
|
91
|
+
json: true,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Endpoint may already be gone server-side; that's fine.
|
|
96
|
+
}
|
|
97
|
+
wd.endpointId = undefined;
|
|
98
|
+
wd.secret = undefined;
|
|
99
|
+
return true;
|
|
100
|
+
},
|
|
101
|
+
async checkExists() {
|
|
102
|
+
const wd = this.getWorkflowStaticData("node");
|
|
103
|
+
return Boolean(wd.endpointId);
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async webhook() {
|
|
109
|
+
const req = this.getRequestObject();
|
|
110
|
+
const headers = req.headers;
|
|
111
|
+
const sig = headers["x-axon-signature"];
|
|
112
|
+
if (typeof sig !== "string") {
|
|
113
|
+
// No signature header — reject.
|
|
114
|
+
return { workflowData: [[{ json: { error: "missing signature" } }]] };
|
|
115
|
+
}
|
|
116
|
+
const wd = this.getWorkflowStaticData("node");
|
|
117
|
+
const secret = wd.secret;
|
|
118
|
+
if (!secret) {
|
|
119
|
+
return { workflowData: [[{ json: { error: "no stored secret" } }]] };
|
|
120
|
+
}
|
|
121
|
+
const body = req.body;
|
|
122
|
+
const rawBody = JSON.stringify(body);
|
|
123
|
+
if (!(0, signatureVerify_js_1.verifySignature)(secret, rawBody, sig)) {
|
|
124
|
+
return { workflowData: [[{ json: { error: "invalid signature" } }]] };
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
workflowData: [[{ json: body }]],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
exports.AxonTrigger = AxonTrigger;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const KNOWN_TRIGGER_EVENT_TYPES: readonly ["task.created", "task.updated", "task.completed", "task.deleted", "project.created", "comment.added", "form.submitted", "process_instance.created", "process_instance.transitioned", "page.published", "routine.run.completed", "record.upserted", "calendar.event.created"];
|
|
2
|
+
export type KnownTriggerEventType = (typeof KNOWN_TRIGGER_EVENT_TYPES)[number];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// The catalog of trigger event types this node supports. Mirrors the
|
|
3
|
+
// platform's KNOWN_TRIGGER_EVENT_TYPES in @axon/shared-types — duplicated
|
|
4
|
+
// here so the n8n package has no peer-dep on shared-types at runtime
|
|
5
|
+
// (n8n's plugin loader prefers fully self-contained packages).
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.KNOWN_TRIGGER_EVENT_TYPES = void 0;
|
|
8
|
+
exports.KNOWN_TRIGGER_EVENT_TYPES = [
|
|
9
|
+
"task.created",
|
|
10
|
+
"task.updated",
|
|
11
|
+
"task.completed",
|
|
12
|
+
"task.deleted",
|
|
13
|
+
"project.created",
|
|
14
|
+
"comment.added",
|
|
15
|
+
"form.submitted",
|
|
16
|
+
"process_instance.created",
|
|
17
|
+
"process_instance.transitioned",
|
|
18
|
+
"page.published",
|
|
19
|
+
"routine.run.completed",
|
|
20
|
+
"record.upserted",
|
|
21
|
+
"calendar.event.created",
|
|
22
|
+
];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function verifySignature(secret: string, body: string, header: string): boolean;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// HMAC-SHA256 signature verification for inbound platform deliveries.
|
|
3
|
+
// Mirrors the platform's outbound-webhook-signer with the same algorithm
|
|
4
|
+
// + constant-time comparison.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.verifySignature = verifySignature;
|
|
7
|
+
const node_crypto_1 = require("node:crypto");
|
|
8
|
+
function verifySignature(secret, body, header) {
|
|
9
|
+
if (!header.startsWith("sha256="))
|
|
10
|
+
return false;
|
|
11
|
+
const expected = "sha256=" + (0, node_crypto_1.createHmac)("sha256", secret).update(body).digest("hex");
|
|
12
|
+
if (expected.length !== header.length)
|
|
13
|
+
return false;
|
|
14
|
+
try {
|
|
15
|
+
return (0, node_crypto_1.timingSafeEqual)(Buffer.from(expected), Buffer.from(header));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|