@codemation/core-nodes 1.0.2 → 1.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/CHANGELOG.md +108 -0
- package/dist/index.cjs +2851 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1556 -684
- package/dist/index.d.ts +1556 -684
- package/dist/index.js +2796 -49
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
- package/src/authoring/defineRestNode.types.ts +204 -0
- package/src/credentials/ApiKeyCredentialType.ts +60 -0
- package/src/credentials/BasicAuthCredentialType.ts +51 -0
- package/src/credentials/BearerTokenCredentialType.ts +40 -0
- package/src/credentials/OAuth2ClientCredentialsTypeFactory.ts +117 -0
- package/src/credentials/OAuth2TokenExchangeFactory.ts +52 -0
- package/src/credentials/index.ts +4 -0
- package/src/http/HttpBodyBuilder.ts +90 -0
- package/src/http/HttpRequestExecutor.ts +150 -0
- package/src/http/HttpUrlBuilder.ts +22 -0
- package/src/http/httpRequest.types.ts +69 -0
- package/src/index.ts +9 -0
- package/src/nodes/AssertionNode.ts +42 -0
- package/src/nodes/CronTriggerFactory.ts +45 -0
- package/src/nodes/CronTriggerNode.ts +40 -0
- package/src/nodes/HttpRequestNodeFactory.ts +99 -23
- package/src/nodes/IsTestRunNode.ts +25 -0
- package/src/nodes/TestTriggerNode.ts +33 -0
- package/src/nodes/assertion.ts +42 -0
- package/src/nodes/collections/collectionDeleteNode.types.ts +23 -0
- package/src/nodes/collections/collectionFindOneNode.types.ts +26 -0
- package/src/nodes/collections/collectionGetNode.types.ts +26 -0
- package/src/nodes/collections/collectionInsertNode.types.ts +22 -0
- package/src/nodes/collections/collectionListNode.types.ts +30 -0
- package/src/nodes/collections/collectionUpdateNode.types.ts +23 -0
- package/src/nodes/collections/index.ts +6 -0
- package/src/nodes/httpRequest.ts +61 -1
- package/src/nodes/isTestRun.ts +24 -0
- package/src/nodes/testTrigger.ts +72 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineNode } from "@codemation/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export const collectionDeleteNode = defineNode({
|
|
5
|
+
key: "collection-delete",
|
|
6
|
+
title: "Collection: Delete",
|
|
7
|
+
description: "Delete a row by id from a collection.",
|
|
8
|
+
icon: "lucide:braces",
|
|
9
|
+
configSchema: z.object({
|
|
10
|
+
collectionName: z.string(),
|
|
11
|
+
id: z.string(),
|
|
12
|
+
}),
|
|
13
|
+
async execute(_args, { config, execution }) {
|
|
14
|
+
const store = execution.collections?.[config.collectionName];
|
|
15
|
+
if (!store) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`Collection "${config.collectionName}" is not registered. Add defineCollection to your codemation config.`,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
const result = await store.delete(config.id);
|
|
21
|
+
return { deleted: result.deleted, id: config.id };
|
|
22
|
+
},
|
|
23
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineNode } from "@codemation/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export const collectionFindOneNode = defineNode({
|
|
5
|
+
key: "collection-find-one",
|
|
6
|
+
title: "Collection: Find One",
|
|
7
|
+
description: "Find a single row matching a filter in a collection.",
|
|
8
|
+
icon: "lucide:filter",
|
|
9
|
+
configSchema: z.object({
|
|
10
|
+
collectionName: z.string(),
|
|
11
|
+
where: z.record(z.string(), z.unknown()),
|
|
12
|
+
}),
|
|
13
|
+
async execute(_args, { config, execution }) {
|
|
14
|
+
const store = execution.collections?.[config.collectionName];
|
|
15
|
+
if (!store) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`Collection "${config.collectionName}" is not registered. Add defineCollection to your codemation config.`,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
const row = await store.findOne(config.where);
|
|
21
|
+
if (row === null) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
return row;
|
|
25
|
+
},
|
|
26
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineNode } from "@codemation/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export const collectionGetNode = defineNode({
|
|
5
|
+
key: "collection-get",
|
|
6
|
+
title: "Collection: Get",
|
|
7
|
+
description: "Get a single row by id from a collection.",
|
|
8
|
+
icon: "lucide:layers",
|
|
9
|
+
configSchema: z.object({
|
|
10
|
+
collectionName: z.string(),
|
|
11
|
+
id: z.string(),
|
|
12
|
+
}),
|
|
13
|
+
async execute(_args, { config, execution }) {
|
|
14
|
+
const store = execution.collections?.[config.collectionName];
|
|
15
|
+
if (!store) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`Collection "${config.collectionName}" is not registered. Add defineCollection to your codemation config.`,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
const row = await store.get(config.id);
|
|
21
|
+
if (row === null) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
return row;
|
|
25
|
+
},
|
|
26
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineNode } from "@codemation/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export const collectionInsertNode = defineNode({
|
|
5
|
+
key: "collection-insert",
|
|
6
|
+
title: "Collection: Insert",
|
|
7
|
+
description: "Insert a new row into a collection.",
|
|
8
|
+
icon: "lucide:boxes",
|
|
9
|
+
configSchema: z.object({
|
|
10
|
+
collectionName: z.string(),
|
|
11
|
+
data: z.record(z.string(), z.unknown()),
|
|
12
|
+
}),
|
|
13
|
+
async execute(_args, { config, execution }) {
|
|
14
|
+
const store = execution.collections?.[config.collectionName];
|
|
15
|
+
if (!store) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`Collection "${config.collectionName}" is not registered. Add defineCollection to your codemation config.`,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
return await store.insert(config.data);
|
|
21
|
+
},
|
|
22
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { defineNode } from "@codemation/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export const collectionListNode = defineNode({
|
|
5
|
+
key: "collection-list",
|
|
6
|
+
title: "Collection: List",
|
|
7
|
+
description: "List rows from a collection with optional pagination and filtering.",
|
|
8
|
+
icon: "lucide:split",
|
|
9
|
+
configSchema: z.object({
|
|
10
|
+
collectionName: z.string(),
|
|
11
|
+
limit: z.number().int().positive().optional(),
|
|
12
|
+
offset: z.number().int().nonnegative().optional(),
|
|
13
|
+
where: z.record(z.string(), z.unknown()).optional(),
|
|
14
|
+
}),
|
|
15
|
+
async execute(_args, { config, execution }) {
|
|
16
|
+
const store = execution.collections?.[config.collectionName];
|
|
17
|
+
if (!store) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`Collection "${config.collectionName}" is not registered. Add defineCollection to your codemation config.`,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
const { rows } = await store.list({
|
|
23
|
+
limit: config.limit,
|
|
24
|
+
offset: config.offset,
|
|
25
|
+
where: config.where,
|
|
26
|
+
});
|
|
27
|
+
// Emit one item per row per AGENTS.md engine/node contract.
|
|
28
|
+
return [...rows];
|
|
29
|
+
},
|
|
30
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineNode } from "@codemation/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export const collectionUpdateNode = defineNode({
|
|
5
|
+
key: "collection-update",
|
|
6
|
+
title: "Collection: Update",
|
|
7
|
+
description: "Update a row by id in a collection.",
|
|
8
|
+
icon: "lucide:square-pen",
|
|
9
|
+
configSchema: z.object({
|
|
10
|
+
collectionName: z.string(),
|
|
11
|
+
id: z.string(),
|
|
12
|
+
patch: z.record(z.string(), z.unknown()),
|
|
13
|
+
}),
|
|
14
|
+
async execute(_args, { config, execution }) {
|
|
15
|
+
const store = execution.collections?.[config.collectionName];
|
|
16
|
+
if (!store) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`Collection "${config.collectionName}" is not registered. Add defineCollection to your codemation config.`,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
return await store.update(config.id, config.patch);
|
|
22
|
+
},
|
|
23
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { collectionInsertNode } from "./collectionInsertNode.types";
|
|
2
|
+
export { collectionGetNode } from "./collectionGetNode.types";
|
|
3
|
+
export { collectionFindOneNode } from "./collectionFindOneNode.types";
|
|
4
|
+
export { collectionListNode } from "./collectionListNode.types";
|
|
5
|
+
export { collectionUpdateNode } from "./collectionUpdateNode.types";
|
|
6
|
+
export { collectionDeleteNode } from "./collectionDeleteNode.types";
|
package/src/nodes/httpRequest.ts
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
RetryPolicy,
|
|
3
|
+
type CredentialRequirement,
|
|
4
|
+
type RetryPolicySpec,
|
|
5
|
+
type RunnableNodeConfig,
|
|
6
|
+
type TypeToken,
|
|
7
|
+
} from "@codemation/core";
|
|
2
8
|
|
|
9
|
+
import type { HttpBodySpec } from "../http/httpRequest.types";
|
|
10
|
+
import {
|
|
11
|
+
apiKeyCredentialType,
|
|
12
|
+
basicAuthCredentialType,
|
|
13
|
+
bearerTokenCredentialType,
|
|
14
|
+
oauth2ClientCredentialsType,
|
|
15
|
+
} from "../credentials/index";
|
|
3
16
|
import { HttpRequestNode } from "./HttpRequestNodeFactory";
|
|
4
17
|
|
|
5
18
|
export type HttpRequestDownloadMode = "auto" | "always" | "never";
|
|
@@ -13,9 +26,22 @@ export type HttpRequestOutputJson = Readonly<{
|
|
|
13
26
|
statusText: string;
|
|
14
27
|
mimeType: string;
|
|
15
28
|
headers: Readonly<Record<string, string>>;
|
|
29
|
+
json?: unknown;
|
|
30
|
+
text?: string;
|
|
16
31
|
bodyBinaryName?: string;
|
|
17
32
|
}>;
|
|
18
33
|
|
|
34
|
+
/**
|
|
35
|
+
* The built-in HTTP request credential type IDs accepted by the `HttpRequest` node.
|
|
36
|
+
* These match the four generic credential types shipped with `@codemation/core-nodes`.
|
|
37
|
+
*/
|
|
38
|
+
export const HTTP_REQUEST_ACCEPTED_CREDENTIAL_TYPES: ReadonlyArray<string> = [
|
|
39
|
+
bearerTokenCredentialType.definition.typeId,
|
|
40
|
+
apiKeyCredentialType.definition.typeId,
|
|
41
|
+
basicAuthCredentialType.definition.typeId,
|
|
42
|
+
oauth2ClientCredentialsType.definition.typeId,
|
|
43
|
+
] as const;
|
|
44
|
+
|
|
19
45
|
export class HttpRequest<
|
|
20
46
|
TInputJson = Readonly<{ url?: string }>,
|
|
21
47
|
TOutputJson = HttpRequestOutputJson,
|
|
@@ -28,8 +54,27 @@ export class HttpRequest<
|
|
|
28
54
|
constructor(
|
|
29
55
|
public readonly name: string,
|
|
30
56
|
public readonly args: Readonly<{
|
|
57
|
+
/** HTTP method (default: GET). */
|
|
31
58
|
method?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Legacy: field name on item.json to read the URL from.
|
|
61
|
+
* Use `url` for a literal/templated URL instead.
|
|
62
|
+
*/
|
|
32
63
|
urlField?: string;
|
|
64
|
+
/** Literal or templated URL. When present, takes precedence over `urlField`. */
|
|
65
|
+
url?: string;
|
|
66
|
+
/** Extra headers to add to every request. */
|
|
67
|
+
headers?: Readonly<Record<string, string>>;
|
|
68
|
+
/** Query parameters to append to the URL. */
|
|
69
|
+
query?: Readonly<Record<string, string>>;
|
|
70
|
+
/** Request body specification. For canvas use, pass a JSON string in `body.data`. */
|
|
71
|
+
body?: HttpBodySpec;
|
|
72
|
+
/**
|
|
73
|
+
* Credential slot key. When set, the node resolves a credential via
|
|
74
|
+
* `ctx.getCredential(credentialSlot)` and applies it to the request.
|
|
75
|
+
* The slot must be declared in `getCredentialRequirements()`.
|
|
76
|
+
*/
|
|
77
|
+
credentialSlot?: string;
|
|
33
78
|
binaryName?: string;
|
|
34
79
|
downloadMode?: HttpRequestDownloadMode;
|
|
35
80
|
id?: string;
|
|
@@ -56,6 +101,21 @@ export class HttpRequest<
|
|
|
56
101
|
get downloadMode(): HttpRequestDownloadMode {
|
|
57
102
|
return this.args.downloadMode ?? "auto";
|
|
58
103
|
}
|
|
104
|
+
|
|
105
|
+
getCredentialRequirements(): ReadonlyArray<CredentialRequirement> {
|
|
106
|
+
if (!this.args.credentialSlot) {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
return [
|
|
110
|
+
{
|
|
111
|
+
slotKey: this.args.credentialSlot,
|
|
112
|
+
label: "Authentication",
|
|
113
|
+
acceptedTypes: HTTP_REQUEST_ACCEPTED_CREDENTIAL_TYPES,
|
|
114
|
+
optional: true,
|
|
115
|
+
helpText: "Optional credential for authenticating the HTTP request.",
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
}
|
|
59
119
|
}
|
|
60
120
|
|
|
61
121
|
export { HttpRequestNode } from "./HttpRequestNodeFactory";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { RunnableNodeConfig, TypeToken } from "@codemation/core";
|
|
2
|
+
|
|
3
|
+
import { IsTestRunNode } from "./IsTestRunNode";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Branches per-item on whether the current run is a test run. Output ports: `true`, `false`.
|
|
7
|
+
* The wire payload is unchanged — this is a router, not a transform.
|
|
8
|
+
*/
|
|
9
|
+
export class IsTestRun<TInputJson = unknown> implements RunnableNodeConfig<TInputJson, TInputJson> {
|
|
10
|
+
readonly kind = "node" as const;
|
|
11
|
+
readonly type: TypeToken<unknown> = IsTestRunNode;
|
|
12
|
+
readonly execution = { hint: "local" } as const;
|
|
13
|
+
readonly icon = "lucide:flask-conical" as const;
|
|
14
|
+
readonly declaredOutputPorts = ["true", "false"] as const;
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly id?: string;
|
|
17
|
+
|
|
18
|
+
constructor(name: string = "Is test run?", id?: string) {
|
|
19
|
+
this.name = name;
|
|
20
|
+
this.id = id;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { IsTestRunNode } from "./IsTestRunNode";
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CredentialRequirement,
|
|
3
|
+
Item,
|
|
4
|
+
TestTriggerNodeConfig,
|
|
5
|
+
TestTriggerSetupContext,
|
|
6
|
+
TypeToken,
|
|
7
|
+
} from "@codemation/core";
|
|
8
|
+
|
|
9
|
+
import { TestTriggerNode } from "./TestTriggerNode";
|
|
10
|
+
|
|
11
|
+
export interface TestTriggerOptions<TOutputJson> {
|
|
12
|
+
readonly name?: string;
|
|
13
|
+
readonly id?: string;
|
|
14
|
+
readonly icon?: string;
|
|
15
|
+
/** Cap on simultaneous in-flight test cases for one suite run. Default: 4 (orchestrator). */
|
|
16
|
+
readonly concurrency?: number;
|
|
17
|
+
readonly credentialRequirements?: ReadonlyArray<CredentialRequirement>;
|
|
18
|
+
/**
|
|
19
|
+
* Free-form description of where the test cases come from. Shown in the node properties
|
|
20
|
+
* panel and the Tests-tab suite-detail header so authors revisiting the workflow six months
|
|
21
|
+
* later remember which mailbox / folder / fixture file the cases originate from.
|
|
22
|
+
*/
|
|
23
|
+
readonly description?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Author callback that yields one item per test case. Items are dispatched as separate
|
|
26
|
+
* workflow runs by the TestSuiteOrchestrator, with `executionOptions.testContext` set.
|
|
27
|
+
* The provided context exposes credential resolution and an AbortSignal for cancellation.
|
|
28
|
+
*/
|
|
29
|
+
generateItems(ctx: TestTriggerSetupContext<TestTrigger<TOutputJson>>): AsyncIterable<Item<TOutputJson>>;
|
|
30
|
+
/**
|
|
31
|
+
* Optional resolver: extract a human-readable label from a yielded item. The orchestrator
|
|
32
|
+
* persists this on the run, so the Tests-tab tree-table shows e.g. "RFQ for batch 14"
|
|
33
|
+
* instead of an opaque runId. Typical use: `(item) => item.json.subject` for mailbox tests.
|
|
34
|
+
*/
|
|
35
|
+
caseLabel?(item: Item<TOutputJson>): string | undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Trigger config for a test fixture source. Drop one (or more) of these on the canvas alongside
|
|
40
|
+
* a workflow's live triggers; clicking "Run tests" on the Tests tab invokes
|
|
41
|
+
* {@link TestTriggerOptions.generateItems} via the TestSuiteOrchestrator.
|
|
42
|
+
*/
|
|
43
|
+
export class TestTrigger<TOutputJson = unknown> implements TestTriggerNodeConfig<TOutputJson> {
|
|
44
|
+
readonly kind = "trigger" as const;
|
|
45
|
+
readonly triggerKind = "test" as const;
|
|
46
|
+
readonly type: TypeToken<unknown> = TestTriggerNode;
|
|
47
|
+
readonly icon: string;
|
|
48
|
+
readonly name: string;
|
|
49
|
+
readonly id?: string;
|
|
50
|
+
readonly concurrency?: number;
|
|
51
|
+
readonly description?: string;
|
|
52
|
+
readonly generateItems: TestTriggerOptions<TOutputJson>["generateItems"];
|
|
53
|
+
readonly caseLabel?: TestTriggerOptions<TOutputJson>["caseLabel"];
|
|
54
|
+
private readonly credentialRequirements: ReadonlyArray<CredentialRequirement>;
|
|
55
|
+
|
|
56
|
+
constructor(options: TestTriggerOptions<TOutputJson>) {
|
|
57
|
+
this.name = options.name ?? "Test trigger";
|
|
58
|
+
this.id = options.id;
|
|
59
|
+
this.icon = options.icon ?? "lucide:flask-conical";
|
|
60
|
+
this.concurrency = options.concurrency;
|
|
61
|
+
this.description = options.description;
|
|
62
|
+
this.credentialRequirements = options.credentialRequirements ?? [];
|
|
63
|
+
this.generateItems = options.generateItems;
|
|
64
|
+
this.caseLabel = options.caseLabel;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getCredentialRequirements(): ReadonlyArray<CredentialRequirement> {
|
|
68
|
+
return this.credentialRequirements;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { TestTriggerNode } from "./TestTriggerNode";
|