@toloka-platform/sdk 0.1.0-preview.5 → 0.1.0-preview.7
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 +7 -11
- package/dist/cli/toloka.js +211 -125
- package/dist/cli/toloka.js.jsc +0 -0
- package/dist/cli/toloka.js.jsc.sha256 +1 -1
- package/dist/cli/toloka.js.sha256 +1 -1
- package/docs/authoring.md +17 -4
- package/docs/cli-v2.md +3 -3
- package/docs/cli.md +55 -17
- package/docs/datasets.md +8 -9
- package/docs/getting-started.md +7 -12
- package/docs/limitations.md +10 -13
- package/docs/testing-examples.md +248 -0
- package/docs/testing.md +115 -0
- package/docs/troubleshooting.md +0 -11
- package/examples/face-image-collection/README.md +7 -1
- package/examples/face-image-collection/fields.ts +20 -8
- package/examples/face-image-collection/nodes/annotate-faces/node.ts +1 -9
- package/examples/face-image-collection/nodes/order-faces/node.ts +1 -1
- package/examples/face-image-collection/pipeline.browser.integration.spec.ts +78 -0
- package/examples/video-pair-rating/README.md +14 -6
- package/examples/video-pair-rating/nodes/adjudicate/node.ts +1 -3
- package/examples/video-pair-rating/nodes/adjudicate_op/node.ts +1 -3
- package/examples/video-pair-rating/nodes/overall_preference_pick/node.ts +1 -1
- package/examples/video-pair-rating/nodes/rate_pair/node.ts +1 -1
- package/examples/video-pair-rating/pipeline.browser.integration.spec.ts +267 -0
- package/openapi/gen/api.ts +819 -898
- package/openapi/gen/models/baseModel.ts +9 -0
- package/openapi/gen/models/baseModelId.ts +2 -0
- package/openapi/gen/models/baseModelIdProperty.ts +2 -0
- package/openapi/gen/models/createDatasetExportBody.ts +3 -3
- package/openapi/gen/models/createDatasetExportBodyOneOf.ts +6 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfFormat.ts +2 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThree.ts +10 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeContract.ts +2 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeFormat.ts +2 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilter.ts +9 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldSourcesItem.ts +6 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldSourcesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldSourcesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldSourcesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItem.ts +12 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItemOperator.ts +2 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItemValue.ts +2 -0
- package/openapi/gen/models/createProjectModelBody.ts +8 -0
- package/openapi/gen/models/createProjectModelBodyDescription.ts +2 -0
- package/openapi/gen/models/datasetExportReport.ts +9 -0
- package/openapi/gen/models/datasetExportReportErrorsItem.ts +5 -0
- package/openapi/gen/models/datasetExportReportTokens.ts +7 -0
- package/openapi/gen/models/exportJob.ts +2 -0
- package/openapi/gen/models/exportJobReport.ts +3 -0
- package/openapi/gen/models/generationNodeSettingsAudienceAndPricingOneOfOneone.ts +1 -0
- package/openapi/gen/models/index.ts +64 -0
- package/openapi/gen/models/listBaseModels200.ts +5 -0
- package/openapi/gen/models/listModelVersions200.ts +7 -0
- package/openapi/gen/models/listModelVersionsParams.ts +5 -0
- package/openapi/gen/models/listProjectModels200.ts +7 -0
- package/openapi/gen/models/listProjectModelsParams.ts +5 -0
- package/openapi/gen/models/model.ts +16 -0
- package/openapi/gen/models/modelArchivedAt.ts +2 -0
- package/openapi/gen/models/modelDescription.ts +2 -0
- package/openapi/gen/models/modelId.ts +2 -0
- package/openapi/gen/models/modelVersion.ts +9 -0
- package/openapi/gen/models/modelVersionId.ts +2 -0
- package/openapi/gen/models/pipelineGraphSnapshotGraphNodesItemOneOfThreeoneSettingsAudienceAndPricingOneOfOneone.ts +1 -0
- package/openapi/gen/models/sampleDatasetItemsBody.ts +4 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOf.ts +8 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilter.ts +9 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldSourcesItem.ts +6 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldSourcesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldSourcesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldSourcesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItem.ts +12 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItemOperator.ts +2 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItemValue.ts +2 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethree.ts +8 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilter.ts +9 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldSourcesItem.ts +6 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldSourcesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldSourcesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldSourcesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItem.ts +12 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItemOperator.ts +2 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItemValue.ts +2 -0
- package/openapi/gen/models/sampleJob.ts +13 -0
- package/openapi/gen/models/sampleJobStatus.ts +2 -0
- package/openapi/gen/models/sampleResult.ts +5 -0
- package/openapi/gen/models/validatePipelineGraphBodyGraphNodesItemOneOfThreeoneSettingsAudienceAndPricingOneOfOneone.ts +1 -0
- package/package.json +23 -2
- package/skills/catalog.json +14 -6
- package/skills/toloka-build/SKILL.md +34 -12
- package/skills/toloka-build/references/node-semantics.md +27 -24
- package/skills/toloka-build/references/workflow-and-cli.md +67 -27
- package/skills/toloka-report-issue/references/report-template.md +1 -1
- package/src/checks/ast.ts +32 -0
- package/src/checks/index.ts +14 -0
- package/src/checks/postprocess-collisions.ts +145 -0
- package/src/checks/task-ui-declarations.generated.ts +19 -0
- package/src/checks/task-ui-typecheck.ts +345 -0
- package/src/checks/typescript-vfs.d.ts +26 -0
- package/src/checks/typescript-vfs.js +5 -0
- package/src/cli/analytics.ts +1 -0
- package/src/cli/check.ts +977 -0
- package/src/cli/data.ts +148 -25
- package/src/cli/models.ts +188 -0
- package/src/cli/program.ts +246 -20
- package/src/cli/project-context.ts +14 -4
- package/src/cli/runs.ts +21 -11
- package/src/cli/skill.ts +17 -6
- package/src/client.ts +109 -35
- package/src/errors.ts +25 -10
- package/src/testing/code-node.ts +160 -0
- package/src/testing/index.ts +15 -0
- package/src/testing/task-ui-browser.ts +120 -0
- package/src/testing/task-ui-validation.ts +135 -0
- package/src/testing/task-ui.ts +557 -0
- package/src/testing/test-evidence.ts +170 -0
- package/src/testing/types.ts +141 -0
- package/src/transport.ts +16 -13
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { assert, isEqual } from "es-toolkit";
|
|
6
|
+
import type { PipelineDefinition } from "../authoring";
|
|
7
|
+
|
|
8
|
+
export const TEST_EVIDENCE_DIR_ENV = "TOLOKA_CHECK_EVIDENCE_DIR";
|
|
9
|
+
export const TEST_PIPELINE_FILTER_ENV = "TOLOKA_CHECK_PIPELINE_FINGERPRINT";
|
|
10
|
+
export const TEST_NODE_FILTER_ENV = "TOLOKA_CHECK_NODE_ID";
|
|
11
|
+
export const TEST_TYPED_ONLY_ENV = "TOLOKA_CHECK_TYPED_ONLY";
|
|
12
|
+
|
|
13
|
+
export type TestEvidenceKind = "code" | "ui" | "pipeline";
|
|
14
|
+
export type TestEvidenceStatus = "passed" | "failed" | "infrastructure_error";
|
|
15
|
+
|
|
16
|
+
export type TestEvidence = {
|
|
17
|
+
readonly version: 1;
|
|
18
|
+
readonly pipelineFingerprint: string;
|
|
19
|
+
readonly kind: TestEvidenceKind;
|
|
20
|
+
readonly status: TestEvidenceStatus;
|
|
21
|
+
readonly testName: string;
|
|
22
|
+
readonly nodeId?: string;
|
|
23
|
+
readonly diagnostic?: string;
|
|
24
|
+
readonly partial?: boolean;
|
|
25
|
+
readonly untestedHardwareFields?: readonly string[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
29
|
+
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
30
|
+
|
|
31
|
+
const isStringArray = (value: unknown): value is string[] =>
|
|
32
|
+
Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
33
|
+
|
|
34
|
+
const canonicalize = (value: unknown): unknown => {
|
|
35
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
36
|
+
if (typeof value !== "object" || value === null) return value;
|
|
37
|
+
return Object.fromEntries(
|
|
38
|
+
Object.entries(value)
|
|
39
|
+
.toSorted(([left], [right]) => left.localeCompare(right))
|
|
40
|
+
.map(([key, entryValue]) => [key, canonicalize(entryValue)]),
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const pipelineFingerprint = (pipeline: PipelineDefinition): string =>
|
|
45
|
+
createHash("sha256")
|
|
46
|
+
.update(JSON.stringify(canonicalize(pipeline)))
|
|
47
|
+
.digest("hex");
|
|
48
|
+
export const typedTestNamePrefix = (fingerprint: string, nodeId?: string): string =>
|
|
49
|
+
`[toloka:${fingerprint}:${nodeId ?? "pipeline"}]`;
|
|
50
|
+
|
|
51
|
+
const escapeRegExp = (value: string): string => value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
52
|
+
|
|
53
|
+
export const typedTestNamePattern = (fingerprint: string, nodeId?: string): string =>
|
|
54
|
+
nodeId === undefined
|
|
55
|
+
? escapeRegExp(`[toloka:${fingerprint}:`)
|
|
56
|
+
: escapeRegExp(typedTestNamePrefix(fingerprint, nodeId));
|
|
57
|
+
|
|
58
|
+
export const assertTypedTestCompletion = ({
|
|
59
|
+
kind,
|
|
60
|
+
name,
|
|
61
|
+
executed,
|
|
62
|
+
assertionsBefore,
|
|
63
|
+
assertionsAfter,
|
|
64
|
+
}: {
|
|
65
|
+
readonly kind: TestEvidenceKind;
|
|
66
|
+
readonly name: string;
|
|
67
|
+
readonly executed: boolean;
|
|
68
|
+
readonly assertionsBefore: number;
|
|
69
|
+
readonly assertionsAfter: number;
|
|
70
|
+
}): void => {
|
|
71
|
+
assert(executed, `${kind} test "${name}" did not run its bound executor`);
|
|
72
|
+
assert(assertionsAfter > assertionsBefore, `${kind} test "${name}" made no assertions`);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const assertTestNodeInPipeline = (
|
|
76
|
+
pipeline: PipelineDefinition,
|
|
77
|
+
node: { readonly id: string; readonly type: string; readonly settings: unknown },
|
|
78
|
+
): void => {
|
|
79
|
+
const graphNode = pipeline.graph.nodes.find((candidate) => candidate.id === node.id);
|
|
80
|
+
assert(
|
|
81
|
+
graphNode?.type === node.type && isEqual(graphNode.settings, node.settings),
|
|
82
|
+
`Node ${JSON.stringify(node.id)} does not belong to the bound pipeline`,
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const isEvidenceKind = (value: unknown): value is TestEvidenceKind =>
|
|
87
|
+
value === "code" || value === "ui" || value === "pipeline";
|
|
88
|
+
|
|
89
|
+
const isEvidenceStatus = (value: unknown): value is TestEvidenceStatus =>
|
|
90
|
+
value === "passed" || value === "failed" || value === "infrastructure_error";
|
|
91
|
+
|
|
92
|
+
const parseEvidence = (value: unknown, path: string): TestEvidence => {
|
|
93
|
+
assert(isRecord(value), `Invalid test evidence ${path}`);
|
|
94
|
+
const record = value;
|
|
95
|
+
assert(record.version === 1, `Unsupported test evidence version in ${path}`);
|
|
96
|
+
assert(typeof record.pipelineFingerprint === "string", `Missing pipeline fingerprint in ${path}`);
|
|
97
|
+
assert(isEvidenceKind(record.kind), `Invalid test evidence kind in ${path}`);
|
|
98
|
+
assert(isEvidenceStatus(record.status), `Invalid test evidence status in ${path}`);
|
|
99
|
+
assert(typeof record.testName === "string", `Missing test name in ${path}`);
|
|
100
|
+
assert(
|
|
101
|
+
record.nodeId === undefined || typeof record.nodeId === "string",
|
|
102
|
+
`Invalid node id in ${path}`,
|
|
103
|
+
);
|
|
104
|
+
assert(
|
|
105
|
+
record.diagnostic === undefined || typeof record.diagnostic === "string",
|
|
106
|
+
`Invalid diagnostic in ${path}`,
|
|
107
|
+
);
|
|
108
|
+
assert(
|
|
109
|
+
record.partial === undefined || typeof record.partial === "boolean",
|
|
110
|
+
`Invalid partial coverage in ${path}`,
|
|
111
|
+
);
|
|
112
|
+
assert(
|
|
113
|
+
record.untestedHardwareFields === undefined || isStringArray(record.untestedHardwareFields),
|
|
114
|
+
`Invalid hardware fields in ${path}`,
|
|
115
|
+
);
|
|
116
|
+
const untestedHardwareFields = record.untestedHardwareFields;
|
|
117
|
+
return {
|
|
118
|
+
version: 1,
|
|
119
|
+
pipelineFingerprint: record.pipelineFingerprint,
|
|
120
|
+
kind: record.kind,
|
|
121
|
+
status: record.status,
|
|
122
|
+
testName: record.testName,
|
|
123
|
+
...(record.nodeId === undefined ? {} : { nodeId: record.nodeId }),
|
|
124
|
+
...(record.diagnostic === undefined ? {} : { diagnostic: record.diagnostic }),
|
|
125
|
+
...(record.partial === undefined ? {} : { partial: record.partial }),
|
|
126
|
+
...(untestedHardwareFields === undefined ? {} : { untestedHardwareFields }),
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const writeTestEvidence = async (
|
|
131
|
+
evidence: TestEvidence,
|
|
132
|
+
environment: NodeJS.ProcessEnv = process.env,
|
|
133
|
+
): Promise<void> => {
|
|
134
|
+
const directory = environment[TEST_EVIDENCE_DIR_ENV];
|
|
135
|
+
if (directory === undefined) return;
|
|
136
|
+
await mkdir(directory, { recursive: true });
|
|
137
|
+
await writeFile(join(directory, `${process.pid}-${randomUUID()}.json`), JSON.stringify(evidence));
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export const readTestEvidence = async (directory: string): Promise<TestEvidence[]> => {
|
|
141
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
142
|
+
const files = entries
|
|
143
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
|
|
144
|
+
.map((entry) => entry.name)
|
|
145
|
+
.toSorted();
|
|
146
|
+
return Promise.all(
|
|
147
|
+
files.map(async (file) => {
|
|
148
|
+
const path = join(directory, file);
|
|
149
|
+
let value: unknown;
|
|
150
|
+
try {
|
|
151
|
+
value = JSON.parse(await readFile(path, "utf8"));
|
|
152
|
+
} catch (error) {
|
|
153
|
+
throw new Error(`Invalid test evidence ${path}`, { cause: error });
|
|
154
|
+
}
|
|
155
|
+
return parseEvidence(value, path);
|
|
156
|
+
}),
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export const matchesTestFilter = (
|
|
161
|
+
pipeline: PipelineDefinition,
|
|
162
|
+
nodeId: string | undefined,
|
|
163
|
+
environment: NodeJS.ProcessEnv = process.env,
|
|
164
|
+
): boolean => {
|
|
165
|
+
const expectedPipeline = environment[TEST_PIPELINE_FILTER_ENV];
|
|
166
|
+
if (expectedPipeline !== undefined && expectedPipeline !== pipelineFingerprint(pipeline))
|
|
167
|
+
return false;
|
|
168
|
+
const expectedNode = environment[TEST_NODE_FILTER_ENV];
|
|
169
|
+
return expectedNode === undefined || expectedNode === nodeId;
|
|
170
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { Browser } from "playwright";
|
|
2
|
+
import type { AuthoredGenerationNode, AuthoredQuorumNode, PipelineDefinition } from "../authoring";
|
|
3
|
+
|
|
4
|
+
export type TaskUiNode = AuthoredGenerationNode | AuthoredQuorumNode;
|
|
5
|
+
|
|
6
|
+
export type TaskUiFormError = {
|
|
7
|
+
componentId: string;
|
|
8
|
+
error: string;
|
|
9
|
+
path?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type TaskUiEntityError = {
|
|
13
|
+
entityName: string;
|
|
14
|
+
path: string;
|
|
15
|
+
reason: string;
|
|
16
|
+
submittedValue?: unknown;
|
|
17
|
+
expectedSchema?: unknown;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type TaskUiCoverage = {
|
|
21
|
+
complete: boolean;
|
|
22
|
+
untestedHardwareFields: string[];
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type RenderTaskUiOptions = {
|
|
26
|
+
data?: Record<string, unknown>;
|
|
27
|
+
allowPartialCoverage?: boolean;
|
|
28
|
+
};
|
|
29
|
+
export type TypedTestOptions = {
|
|
30
|
+
readonly timeout?: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type TaskUiComponentDescription = {
|
|
34
|
+
name: string;
|
|
35
|
+
description: string;
|
|
36
|
+
whenToUse: string;
|
|
37
|
+
capabilities: string[];
|
|
38
|
+
limitations: string[];
|
|
39
|
+
bestPractices: string[];
|
|
40
|
+
propsType: string;
|
|
41
|
+
valueType?: string;
|
|
42
|
+
example?: {
|
|
43
|
+
jsxTemplate: string;
|
|
44
|
+
output: string;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type TaskUiSession = {
|
|
49
|
+
componentTree: () => Promise<string>;
|
|
50
|
+
componentDocumentation: (names?: string[]) => Promise<Record<string, TaskUiComponentDescription>>;
|
|
51
|
+
getValue: (path: string) => Promise<unknown>;
|
|
52
|
+
setValue: (path: string, value: unknown) => Promise<void>;
|
|
53
|
+
validationErrors: () => Promise<TaskUiFormError[]>;
|
|
54
|
+
submit: () => Promise<Record<string, unknown>>;
|
|
55
|
+
screenshot: (path: string) => Promise<string>;
|
|
56
|
+
coverage: TaskUiCoverage;
|
|
57
|
+
close: () => Promise<void>;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type TaskUiFixture = {
|
|
61
|
+
componentDocumentation: (names?: string[]) => Promise<Record<string, TaskUiComponentDescription>>;
|
|
62
|
+
render: (
|
|
63
|
+
pipeline: PipelineDefinition,
|
|
64
|
+
node: TaskUiNode,
|
|
65
|
+
options?: RenderTaskUiOptions,
|
|
66
|
+
) => Promise<TaskUiSession>;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export class TaskUiInfrastructureError extends Error {
|
|
70
|
+
constructor(message: string, options?: ErrorOptions) {
|
|
71
|
+
super(message, options);
|
|
72
|
+
this.name = "TaskUiInfrastructureError";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class TaskUiValidationError extends Error {
|
|
77
|
+
readonly formErrors: TaskUiFormError[];
|
|
78
|
+
readonly entityErrors: TaskUiEntityError[];
|
|
79
|
+
readonly coverage: TaskUiCoverage;
|
|
80
|
+
|
|
81
|
+
constructor({
|
|
82
|
+
message,
|
|
83
|
+
formErrors = [],
|
|
84
|
+
entityErrors = [],
|
|
85
|
+
coverage,
|
|
86
|
+
}: {
|
|
87
|
+
message: string;
|
|
88
|
+
formErrors?: TaskUiFormError[];
|
|
89
|
+
entityErrors?: TaskUiEntityError[];
|
|
90
|
+
coverage: TaskUiCoverage;
|
|
91
|
+
}) {
|
|
92
|
+
super(message);
|
|
93
|
+
this.name = "TaskUiValidationError";
|
|
94
|
+
this.formErrors = formErrors;
|
|
95
|
+
this.entityErrors = entityErrors;
|
|
96
|
+
this.coverage = coverage;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export type TbxTemplate = {
|
|
101
|
+
code: string;
|
|
102
|
+
data: string;
|
|
103
|
+
preprocess: string;
|
|
104
|
+
postprocess: string;
|
|
105
|
+
constants: string;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export type TbxWindowApi = {
|
|
109
|
+
getData: () => Promise<Record<string, unknown> | undefined>;
|
|
110
|
+
agents: {
|
|
111
|
+
setConfig: (config: TbxTemplate, mode?: "xml" | "json") => Promise<string>;
|
|
112
|
+
getComponentTree: (mode?: "xml" | "json") => Promise<string>;
|
|
113
|
+
getComponentState: (componentId: string) => Promise<Record<string, unknown>>;
|
|
114
|
+
getComponentValue: (componentId: string) => Promise<unknown>;
|
|
115
|
+
setComponentValue: (componentId: string, value: unknown) => Promise<void>;
|
|
116
|
+
getValidationErrors: () => Promise<Array<{ componentId: string; error: string }>>;
|
|
117
|
+
getComponentsDescriptions: () => Promise<TaskUiComponentDescription[]>;
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export type UiWorkerFixtures = {
|
|
122
|
+
_uiConfig: {
|
|
123
|
+
launcherUrl: string;
|
|
124
|
+
launchBrowser: () => Promise<Browser>;
|
|
125
|
+
};
|
|
126
|
+
_uiBrowser: Browser;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export type UiTestFixtures = {
|
|
130
|
+
ui: TaskUiFixture;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export type TbxBrowserRequest =
|
|
134
|
+
| { operation: "setConfig"; config: TbxTemplate }
|
|
135
|
+
| { operation: "getComponentTree" }
|
|
136
|
+
| { operation: "getComponentState"; componentId: string }
|
|
137
|
+
| { operation: "getComponentValue"; componentId: string }
|
|
138
|
+
| { operation: "setComponentValue"; componentId: string; value: unknown }
|
|
139
|
+
| { operation: "getValidationErrors" }
|
|
140
|
+
| { operation: "getComponentsDescriptions" }
|
|
141
|
+
| { operation: "getData" };
|
package/src/transport.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { assert } from
|
|
2
|
-
import { PotatoApiError } from
|
|
1
|
+
import { assert } from "es-toolkit";
|
|
2
|
+
import { PotatoApiError } from "./errors";
|
|
3
3
|
|
|
4
|
-
const BASE_URL_HEADER =
|
|
4
|
+
const BASE_URL_HEADER = "x-potato-sdk-base-url";
|
|
5
5
|
|
|
6
6
|
type TransportRequestInit = RequestInit & {
|
|
7
7
|
readonly fetch?: typeof globalThis.fetch;
|
|
@@ -10,18 +10,18 @@ type TransportRequestInit = RequestInit & {
|
|
|
10
10
|
export const createRequestOptions = (
|
|
11
11
|
baseUrl: string,
|
|
12
12
|
apiKey: string,
|
|
13
|
-
fetch?: typeof globalThis.fetch
|
|
13
|
+
fetch?: typeof globalThis.fetch,
|
|
14
14
|
): TransportRequestInit => {
|
|
15
|
-
assert(baseUrl.length > 0,
|
|
16
|
-
assert(apiKey.length > 0,
|
|
15
|
+
assert(baseUrl.length > 0, "The SDK base URL cannot be empty");
|
|
16
|
+
assert(apiKey.length > 0, "The SDK API key cannot be empty");
|
|
17
17
|
|
|
18
18
|
return {
|
|
19
19
|
headers: {
|
|
20
|
-
Accept:
|
|
20
|
+
Accept: "application/json",
|
|
21
21
|
Authorization: `ApiKey ${apiKey}`,
|
|
22
|
-
[BASE_URL_HEADER]: new URL(baseUrl).toString()
|
|
22
|
+
[BASE_URL_HEADER]: new URL(baseUrl).toString(),
|
|
23
23
|
},
|
|
24
|
-
fetch
|
|
24
|
+
fetch,
|
|
25
25
|
};
|
|
26
26
|
};
|
|
27
27
|
|
|
@@ -30,7 +30,7 @@ export async function potatoFetch(url: string, options: TransportRequestInit): P
|
|
|
30
30
|
const { fetch: fetchImplementation = globalThis.fetch, ...requestOptions } = options;
|
|
31
31
|
const headers = new Headers(requestOptions.headers);
|
|
32
32
|
const baseUrl = headers.get(BASE_URL_HEADER);
|
|
33
|
-
assert(baseUrl,
|
|
33
|
+
assert(baseUrl, "The SDK base URL is missing");
|
|
34
34
|
headers.delete(BASE_URL_HEADER);
|
|
35
35
|
|
|
36
36
|
const response = await fetchImplementation(new URL(url, baseUrl), { ...requestOptions, headers });
|
|
@@ -41,13 +41,16 @@ export async function potatoFetch(url: string, options: TransportRequestInit): P
|
|
|
41
41
|
|
|
42
42
|
if (text.length === 0) {
|
|
43
43
|
body = undefined;
|
|
44
|
-
} else if (response.headers.get(
|
|
44
|
+
} else if (response.headers.get("content-type")?.includes("application/json")) {
|
|
45
45
|
body = JSON.parse(text);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
throw PotatoApiError.fromResponse(
|
|
48
|
+
throw PotatoApiError.fromResponse(
|
|
49
|
+
response.status,
|
|
50
|
+
body,
|
|
51
|
+
response.headers.get("x-request-id") ?? undefined,
|
|
52
|
+
);
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
return text.length === 0 ? undefined : JSON.parse(text);
|
|
52
56
|
}
|
|
53
|
-
|