Rozdeleni na sluzby a konektory, pristupove udaje do konektoru
Slovo "konektor" v kodu znamenalo katalog toho, co umime. Ted znamena napojeni jedne firmy, tedy to, co tim mysli i uzivatel. Popis modelu je v documentation/12-sluzby-a-konektory.md. Tri vrstvy: - Sluzba: ze iDoklad existuje, co umi a co potrebuje k napojeni. Nase. - Skript: kod, ktery jednu operaci sluzby opravdu vykona. Nas. - Konektor: ucet firmy vcetne jejich pristupovych udaju. Firemni. Pristupove udaje se prestaly cist z environment variables. Cela instance by mela jedny udaje spolecne a dve firmy by fakturovaly z jednoho uctu. Napojeni je vlastnost firmy, ne prostredi. Z prostredi zustava jen SERVICES_BASE_URL. Pridano: - src/data/services.ts: sluzba nese general, appId, visibility, credentials a verifyPath. Kategorie "obecne" sdruzuje veci, ktere ma kazdy a nepotrebuji konektor: webhook, planovac, tickety, transformace dat, HTTP pozadavek, pauza, zapis do logu - viditelnost sluzby: vsichni, jen uvedene firmy a lide, nebo jen spravce platformy. Neviditelna sluzba se z API nevraci vubec, ne se stavem 403 - firma nema poznat, ze takova sluzba existuje - src/data/connectorStore.ts: konektory za firmu vcetne hodnot udaju. Hodnoty se z API nikdy nevraci, jen filled a missing. Prazdne pole hodnotu nemeni, takze ulozeni formularu bez tajnych hodnot nic nepresepe - FlowStep.connectorId: krok rika, pod kterym napojenim volat. null = vychozi konektor firmy, diky tomu je vzorovy strom prenositelny mezi firmami - overeni konektoru pres verifyPath, tedy cteci volani vyzadujici autorizaci. U sluzby bez nej se overi jen dostupnost a odpoved to rekne nahlas, jinak by zeleny vysledek uzivateli lhal - stranky /dashboard/sluzby a /dashboard/konektory vcetne formularu udaju - endpointy /api/dashboard/services a CRUD /api/dashboard/connectors ve Swaggeru - predvyplnene prihlaseni spravcem platformy a prepinac demo uctu na login strance, kvuli testovani prototypu Zmeneno: - stav "napojeno" se prestal cist z katalogu a zacal pocitat z konektoru firmy. Sluzba ma jen available nebo planned - validace stromu overuje i konektor. Cizi konektor je chyba, chybejici napojeni nedodelek - rozdelana prace se nezahazuje - prejmenovani napric kodem: Connector na Service, FlowStep.connectorId na serviceId, GET /connectors na GET /services, connectorIcons na serviceIcons, stranka Konektory (katalog) na Sluzby. Prevodni tabulka je v dokumentu 12 Overeno: npm run typecheck prochazi na serveru i webu. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
6f6b287d7e
commit
8ad91a6c28
+68
-105
@@ -1,139 +1,102 @@
|
||||
/**
|
||||
* Napojeni konektoru: kam se vola a cim se to autorizuje.
|
||||
* Kam se vola a cim se to autorizuje.
|
||||
*
|
||||
* Zamerne oddelene od skriptu. Skript rika "GET /issued-invoices/12",
|
||||
* napojeni rika, na jake adrese to je a jaké hlavicky se pridaji. Skript se
|
||||
* tim k pristupovym udajum vubec nedostane.
|
||||
* Skript rika `GET /issued-invoices/12`. Zbytek dodava tenhle soubor ze dvou
|
||||
* mist: **sluzba** rika, ktere hlavicky jsou potreba, **konektor** firmy nese
|
||||
* jejich hodnoty.
|
||||
*
|
||||
* Tady je zatim jedno napojeni na konektor, sestavene z environment variables.
|
||||
* Cilovy stav je napojeni za firmu v databazi, viz documentation/09, bod 9.
|
||||
* Az to bude, prepise se vnitrek `resolveConnection` a nic dalsiho.
|
||||
* Diky tomu se skript k pristupovym udajum vubec nedostane a dve firmy volaji
|
||||
* tutez sluzbu kazda pod svym uctem.
|
||||
*
|
||||
* Z environment variables sem uz nechodi zadne pristupove udaje, jen zaklad
|
||||
* adresy (`SERVICES_BASE_URL`). Udaje patri konektoru.
|
||||
*/
|
||||
|
||||
import { config } from '../config.js';
|
||||
import type { Connector } from '../data/connectorStore.js';
|
||||
import { findService, type Service } from '../data/services.js';
|
||||
|
||||
export interface ConnectorAuthSpec {
|
||||
/** Hlavicka -> jmeno environment variable, ze ktere se plni. */
|
||||
headers: Record<string, string>;
|
||||
/** Ktere hlavicky musi byt vyplnene, aby se dalo volat. */
|
||||
required: string[];
|
||||
/** Necitliva nastaveni pristupna skriptu jako `ctx.config`. */
|
||||
config?: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Autorizace jednotlivych konektoru.
|
||||
*
|
||||
* iDoklad podle https://services.csbot.cz/apps/idoklad/docs: sluzba prijima
|
||||
* `X-ClientId` a `X-ClientSecret`, `X-ApplicationId` jen partnerske aplikace.
|
||||
* OAuth tok resi ta sluzba, my posilame jen tyto hlavicky.
|
||||
*/
|
||||
const authSpecs: Record<string, ConnectorAuthSpec> = {
|
||||
idoklad: {
|
||||
headers: {
|
||||
'X-ClientId': 'IDOKLAD_CLIENT_ID',
|
||||
'X-ClientSecret': 'IDOKLAD_CLIENT_SECRET',
|
||||
'X-ApplicationId': 'IDOKLAD_APPLICATION_ID',
|
||||
},
|
||||
required: ['X-ClientId', 'X-ClientSecret'],
|
||||
config: { language: 'IDOKLAD_LANGUAGE' },
|
||||
},
|
||||
};
|
||||
|
||||
export interface ResolvedConnection {
|
||||
connectorId: string;
|
||||
name: string;
|
||||
export interface ResolvedTarget {
|
||||
serviceId: string;
|
||||
connectorId: string | null;
|
||||
/** Prazdne u obecnych sluzeb - `ctx.http` pak vyzaduje absolutni adresu. */
|
||||
baseUrl: string;
|
||||
/** Vcetne tajemstvi. Nikdy neposilat na klienta ani do logu. */
|
||||
headers: Record<string, string>;
|
||||
/** Necitliva cast, skript ji vidi jako `ctx.config`. */
|
||||
config: Record<string, string>;
|
||||
/** false = chybi pristupove udaje, volat nema smysl. */
|
||||
serviceConfig: Record<string, string>;
|
||||
/** false = chybi povinne udaje, volat nema smysl. */
|
||||
ready: boolean;
|
||||
/** Jmena environment variables, ktere chybi. */
|
||||
/** Popisy chybejicich poli pro uzivatele. */
|
||||
missing: string[];
|
||||
}
|
||||
|
||||
/** `search-console` -> `SEARCH_CONSOLE`, aby slo skladat jmena promennych. */
|
||||
function envPrefix(connectorId: string): string {
|
||||
return connectorId.replace(/-/g, '_').toUpperCase();
|
||||
}
|
||||
|
||||
function readEnv(name: string): string | undefined {
|
||||
const value = process.env[name];
|
||||
return value !== undefined && value.trim() !== '' ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vychozi adresa sluzby. Verejna domena se nikdy nehardcoduje do logiky,
|
||||
* bere se z `SERVICES_BASE_URL` (viz AGENTS.md).
|
||||
* Jednotlive konektory lze presmerovat pres `<KONEKTOR>_BASE_URL`.
|
||||
* bere se ze `SERVICES_BASE_URL` (AGENTS.md).
|
||||
*/
|
||||
function resolveBaseUrl(connectorId: string): string {
|
||||
return (
|
||||
readEnv(`${envPrefix(connectorId)}_BASE_URL`) ?? `${config.servicesBaseUrl}/${connectorId}`
|
||||
);
|
||||
export function serviceBaseUrl(service: Service): string {
|
||||
if (service.appId === null) return '';
|
||||
return `${config.servicesBaseUrl}/${service.appId}`;
|
||||
}
|
||||
|
||||
export function resolveConnection(connectorId: string): ResolvedConnection {
|
||||
const spec = authSpecs[connectorId];
|
||||
const headers: Record<string, string> = {};
|
||||
const scriptConfig: Record<string, string> = {};
|
||||
const missing: string[] = [];
|
||||
/**
|
||||
* Slozi adresu a hlavicky pro beh skriptu.
|
||||
*
|
||||
* `connector` je null u obecnych sluzeb, ktere napojeni nepotrebuji. U ostatnich
|
||||
* je jeho absence duvod, proc se skript nespusti - a rekne se to nahlas, ne ze
|
||||
* se zavola bez autorizace a spadne to az na 401.
|
||||
*/
|
||||
export function resolveTarget(serviceId: string, connector: Connector | null): ResolvedTarget {
|
||||
const service = findService(serviceId);
|
||||
|
||||
for (const [header, envName] of Object.entries(spec?.headers ?? {})) {
|
||||
const value = readEnv(envName);
|
||||
if (value !== undefined) headers[header] = value;
|
||||
else if (spec?.required.includes(header)) missing.push(envName);
|
||||
if (!service) {
|
||||
return {
|
||||
serviceId,
|
||||
connectorId: connector?.id ?? null,
|
||||
baseUrl: '',
|
||||
headers: {},
|
||||
serviceConfig: {},
|
||||
ready: false,
|
||||
missing: [`Služba ${serviceId} v katalogu neexistuje.`],
|
||||
};
|
||||
}
|
||||
|
||||
for (const [key, envName] of Object.entries(spec?.config ?? {})) {
|
||||
const value = readEnv(envName);
|
||||
if (value !== undefined) scriptConfig[key] = value;
|
||||
const headers: Record<string, string> = {};
|
||||
const serviceConfig: Record<string, string> = {};
|
||||
const missing: string[] = [];
|
||||
|
||||
for (const field of service.credentials) {
|
||||
const value = (connector?.values[field.id] ?? '').trim();
|
||||
|
||||
if (value === '') {
|
||||
if (field.required) missing.push(field.label);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.target === 'header') headers[field.name] = value;
|
||||
else serviceConfig[field.name] = value;
|
||||
}
|
||||
|
||||
if (!service.general && connector === null) {
|
||||
missing.push(`Služba ${service.name} potřebuje konektor.`);
|
||||
}
|
||||
if (connector !== null && !connector.enabled) {
|
||||
missing.push(`Konektor ${connector.name} je vypnutý.`);
|
||||
}
|
||||
|
||||
return {
|
||||
connectorId,
|
||||
name: `${connectorId} (z environment variables)`,
|
||||
baseUrl: resolveBaseUrl(connectorId),
|
||||
serviceId,
|
||||
connectorId: connector?.id ?? null,
|
||||
baseUrl: connector?.baseUrl ?? serviceBaseUrl(service),
|
||||
headers,
|
||||
config: scriptConfig,
|
||||
serviceConfig,
|
||||
ready: missing.length === 0,
|
||||
missing,
|
||||
};
|
||||
}
|
||||
|
||||
/** Hodnoty, ktere se musi zredigovat, nez cokoliv skonci v logu. */
|
||||
export function connectionSecrets(connection: ResolvedConnection): string[] {
|
||||
return Object.values(connection.headers);
|
||||
}
|
||||
|
||||
export interface ConnectionStatus {
|
||||
connectorId: string;
|
||||
baseUrl: string;
|
||||
ready: boolean;
|
||||
/** Jen jmena chybejicich promennych, nikdy hodnoty. */
|
||||
missing: string[];
|
||||
/** Ktere hlavicky jsou vyplnene. Hodnoty se nevraci. */
|
||||
headers: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Stav napojeni pro portal. Vraci se **jen jmena**, nikdy hodnoty -
|
||||
* secrets se z beznych endpointu nevraci (AGENTS.md).
|
||||
*/
|
||||
export function connectionStatus(connectorId: string): ConnectionStatus {
|
||||
const connection = resolveConnection(connectorId);
|
||||
return {
|
||||
connectorId,
|
||||
baseUrl: connection.baseUrl,
|
||||
ready: connection.ready,
|
||||
missing: connection.missing,
|
||||
headers: Object.keys(connection.headers),
|
||||
};
|
||||
}
|
||||
|
||||
/** Ktere konektory maji popsanou autorizaci. */
|
||||
export function connectorsWithAuth(): string[] {
|
||||
return Object.keys(authSpecs);
|
||||
export function targetSecrets(target: ResolvedTarget): string[] {
|
||||
return Object.values(target.headers);
|
||||
}
|
||||
|
||||
+18
-8
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
|
||||
import { config } from '../config.js';
|
||||
import type { ResolvedConnection } from './connections.js';
|
||||
import type { ResolvedTarget } from './connections.js';
|
||||
import { ScriptError, type ScriptHttp, type ScriptHttpOptions, type ScriptHttpResponse } from './types.js';
|
||||
import { describe } from './util.js';
|
||||
|
||||
@@ -53,12 +53,22 @@ function assertAllowedUrl(url: URL): void {
|
||||
}
|
||||
}
|
||||
|
||||
function buildUrl(connection: ResolvedConnection, path: string, options?: ScriptHttpOptions): URL {
|
||||
function buildUrl(target: ResolvedTarget, path: string, options?: ScriptHttpOptions): URL {
|
||||
// Obecna sluzba nema pevnou adresu, skript pak musi poslat absolutni.
|
||||
const absolute = target.baseUrl === '';
|
||||
if (absolute && !/^https?:\/\//i.test(path)) {
|
||||
throw new ScriptError(
|
||||
'config',
|
||||
`Služba ${target.serviceId} nemá základní adresu, skript musí volat absolutní URL.`,
|
||||
);
|
||||
}
|
||||
|
||||
const raw = absolute ? path : joinUrl(target.baseUrl, path);
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(joinUrl(connection.baseUrl, path));
|
||||
url = new URL(raw);
|
||||
} catch {
|
||||
throw new ScriptError('config', `Neplatná adresa: ${joinUrl(connection.baseUrl, path)}`);
|
||||
throw new ScriptError('config', `Neplatná adresa: ${raw}`);
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(options?.query ?? {})) {
|
||||
@@ -108,7 +118,7 @@ function transportError(err: unknown, url: URL): ScriptError {
|
||||
}
|
||||
|
||||
export interface CreateHttpOptions {
|
||||
connection: ResolvedConnection;
|
||||
target: ResolvedTarget;
|
||||
signal: AbortSignal;
|
||||
idempotencyKey: string;
|
||||
/** Zredigovana verze textu, aby se tajemstvi nedostalo do logu. */
|
||||
@@ -118,7 +128,7 @@ export interface CreateHttpOptions {
|
||||
}
|
||||
|
||||
export function createHttp(options: CreateHttpOptions): ScriptHttp {
|
||||
const { connection, signal, idempotencyKey, redact, log, onCall } = options;
|
||||
const { target, signal, idempotencyKey, redact, log, onCall } = options;
|
||||
|
||||
async function request<T>(
|
||||
method: string,
|
||||
@@ -126,7 +136,7 @@ export function createHttp(options: CreateHttpOptions): ScriptHttp {
|
||||
body: unknown,
|
||||
httpOptions?: ScriptHttpOptions,
|
||||
): Promise<ScriptHttpResponse<T>> {
|
||||
const url = buildUrl(connection, path, httpOptions);
|
||||
const url = buildUrl(target, path, httpOptions);
|
||||
const hasBody = body !== undefined && method !== 'GET' && method !== 'DELETE';
|
||||
const startedAt = Date.now();
|
||||
onCall();
|
||||
@@ -140,7 +150,7 @@ export function createHttp(options: CreateHttpOptions): ScriptHttp {
|
||||
Accept: 'application/json',
|
||||
// Druhy pokus tehoz kroku nesmi vystavit druhou fakturu.
|
||||
'Idempotency-Key': idempotencyKey,
|
||||
...connection.headers,
|
||||
...target.headers,
|
||||
...(hasBody ? { 'Content-Type': 'application/json' } : {}),
|
||||
...httpOptions?.headers,
|
||||
},
|
||||
|
||||
@@ -6,12 +6,11 @@
|
||||
* jedno by se casem rozeslo a strom by nabizel parametr, ktery skript nezna.
|
||||
*/
|
||||
|
||||
import type { ConnectorOperation, ProvidedField } from '../data/connectors.js';
|
||||
import type { OperationField } from '../data/connectors.js';
|
||||
import type { OperationField, ProvidedField, ServiceOperation } from '../data/services.js';
|
||||
import { scriptManifestSchema, type FieldIssue, type ScriptField, type ScriptManifest } from './types.js';
|
||||
|
||||
/** `idoklad.get-issued-invoice` -> `idoklad` */
|
||||
export function connectorIdOf(scriptId: string): string {
|
||||
export function serviceIdOf(scriptId: string): string {
|
||||
return scriptId.slice(0, scriptId.indexOf('.'));
|
||||
}
|
||||
|
||||
@@ -81,7 +80,7 @@ function toOperationField(field: ScriptField): OperationField {
|
||||
*/
|
||||
function toProvidedField(scriptId: string, field: ScriptField): ProvidedField {
|
||||
return {
|
||||
id: `${connectorIdOf(scriptId)}.${field.id}`,
|
||||
id: `${serviceIdOf(scriptId)}.${field.id}`,
|
||||
name: field.id,
|
||||
type: field.type,
|
||||
required: field.required,
|
||||
@@ -89,7 +88,7 @@ function toProvidedField(scriptId: string, field: ScriptField): ProvidedField {
|
||||
}
|
||||
|
||||
/** Operace katalogu odvozena ze skriptu. Tohle vidi builder. */
|
||||
export function toConnectorOperation(manifest: ScriptManifest): ConnectorOperation {
|
||||
export function toServiceOperation(manifest: ScriptManifest): ServiceOperation {
|
||||
return {
|
||||
id: operationIdOf(manifest.id),
|
||||
name: manifest.name,
|
||||
|
||||
+13
-13
@@ -17,8 +17,8 @@ import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { config } from '../config.js';
|
||||
import { connectors, setScriptActions, type ConnectorOperation } from '../data/connectors.js';
|
||||
import { connectorIdOf, parseManifest, toConnectorOperation } from './manifest.js';
|
||||
import { services, setScriptActions, type ServiceOperation } from '../data/services.js';
|
||||
import { parseManifest, serviceIdOf, toServiceOperation } from './manifest.js';
|
||||
import type { FieldIssue, ScriptContext, ScriptManifest, ScriptValues } from './types.js';
|
||||
|
||||
export type ScriptRunFn = (
|
||||
@@ -150,7 +150,7 @@ async function scan(): Promise<void> {
|
||||
problems.set(id, {
|
||||
file: fileName,
|
||||
scriptId: null,
|
||||
message: 'Název souboru musí mít tvar konektor.operace.js, jen malá písmena a pomlčky.',
|
||||
message: 'Název souboru musí mít tvar sluzba.operace.js, jen malá písmena a pomlčky.',
|
||||
});
|
||||
continue;
|
||||
}
|
||||
@@ -196,30 +196,30 @@ async function scan(): Promise<void> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prenese nactene skripty do katalogu konektoru.
|
||||
* Prenese nactene skripty do katalogu sluzeb.
|
||||
*
|
||||
* Tim se naraz objevi ve validaci stromu, ve vypoctu toho, co je v kterem kroku
|
||||
* videt, i v sablonach - vsechno se uz pta pres `findOperation`.
|
||||
*/
|
||||
function publishToCatalog(): void {
|
||||
const byConnector = new Map<string, ConnectorOperation[]>();
|
||||
const byService = new Map<string, ServiceOperation[]>();
|
||||
|
||||
for (const script of scripts.values()) {
|
||||
const connectorId = connectorIdOf(script.manifest.id);
|
||||
if (!connectors.some((connector) => connector.id === connectorId)) {
|
||||
const serviceId = serviceIdOf(script.manifest.id);
|
||||
if (!services.some((service) => service.id === serviceId)) {
|
||||
problems.set(script.manifest.id, {
|
||||
file: script.file,
|
||||
scriptId: script.manifest.id,
|
||||
message: `Konektor ${connectorId} v katalogu neexistuje. Skript se nedá použít ve stromu.`,
|
||||
message: `Služba ${serviceId} v katalogu neexistuje. Skript se nedá použít ve stromu.`,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const list = byConnector.get(connectorId) ?? [];
|
||||
list.push(toConnectorOperation(script.manifest));
|
||||
byConnector.set(connectorId, list);
|
||||
const list = byService.get(serviceId) ?? [];
|
||||
list.push(toServiceOperation(script.manifest));
|
||||
byService.set(serviceId, list);
|
||||
}
|
||||
|
||||
setScriptActions(byConnector);
|
||||
setScriptActions(byService);
|
||||
}
|
||||
|
||||
/** Prohleda adresar, nejvyse jednou za RESCAN_MS. Soubezne volani se sdili. */
|
||||
@@ -280,7 +280,7 @@ export type SaveResult =
|
||||
*/
|
||||
export async function saveSource(id: string, code: string): Promise<SaveResult> {
|
||||
if (!isValidScriptId(id)) {
|
||||
return { ok: false, message: 'Neplatné ID skriptu. Povolený tvar je konektor.operace.' };
|
||||
return { ok: false, message: 'Neplatné ID skriptu. Povolený tvar je sluzba.operace.' };
|
||||
}
|
||||
if (code.trim().length === 0) {
|
||||
return { ok: false, message: 'Kód skriptu nesmí být prázdný.' };
|
||||
|
||||
+15
-8
@@ -12,9 +12,10 @@
|
||||
|
||||
import { createHash } from 'node:crypto';
|
||||
import { config } from '../config.js';
|
||||
import { connectionSecrets, resolveConnection } from './connections.js';
|
||||
import type { Connector } from '../data/connectorStore.js';
|
||||
import { resolveTarget, targetSecrets } from './connections.js';
|
||||
import { createHttp } from './http.js';
|
||||
import { connectorIdOf } from './manifest.js';
|
||||
import { serviceIdOf } from './manifest.js';
|
||||
import { getScript } from './registry.js';
|
||||
import {
|
||||
isRetryableKind,
|
||||
@@ -28,6 +29,12 @@ import { createRedactor, describe, scriptUtil } from './util.js';
|
||||
import { validateValues } from './values.js';
|
||||
|
||||
export interface RunScriptOptions {
|
||||
/**
|
||||
* Konektor firmy, pod jehoz udaji se to zavola. `null` je v poradku jen
|
||||
* u obecnych sluzeb - u ostatnich se skript nespusti a rekne se to nahlas.
|
||||
* Prislusnost konektoru k firme si overuje volajici.
|
||||
*/
|
||||
connector?: Connector | null;
|
||||
/**
|
||||
* Stabilni pres vsechny pokusy tehoz kroku. Kdyz chybi, dopocita se
|
||||
* ze skriptu a vstupu - dva stejne pokusy tak dostanou stejny klic.
|
||||
@@ -95,16 +102,16 @@ export async function runScript(
|
||||
}
|
||||
|
||||
const { manifest } = script;
|
||||
const connection = resolveConnection(connectorIdOf(scriptId));
|
||||
const redact = createRedactor(connectionSecrets(connection));
|
||||
const target = resolveTarget(serviceIdOf(scriptId), options.connector ?? null);
|
||||
const redact = createRedactor(targetSecrets(target));
|
||||
|
||||
if (!connection.ready) {
|
||||
if (!target.ready) {
|
||||
return finish({
|
||||
ok: false,
|
||||
outputs: {},
|
||||
error: {
|
||||
kind: 'config',
|
||||
message: `Napojení na ${connection.connectorId} není nastavené. Chybí: ${connection.missing.join(', ')}.`,
|
||||
message: `Napojení není hotové: ${target.missing.join(', ')}.`,
|
||||
retryable: false,
|
||||
},
|
||||
});
|
||||
@@ -144,7 +151,7 @@ export async function runScript(
|
||||
|
||||
const ctx: ScriptContext = {
|
||||
http: createHttp({
|
||||
connection,
|
||||
target,
|
||||
signal: controller.signal,
|
||||
idempotencyKey,
|
||||
redact,
|
||||
@@ -155,7 +162,7 @@ export async function runScript(
|
||||
}),
|
||||
util: scriptUtil,
|
||||
log,
|
||||
config: Object.freeze({ ...connection.config }),
|
||||
config: Object.freeze({ ...target.serviceConfig }),
|
||||
idempotencyKey,
|
||||
fail(message, detail) {
|
||||
throw new ScriptError('terminal', message, { detail: describe(detail) });
|
||||
|
||||
@@ -76,7 +76,7 @@ export const scriptManifestSchema = z
|
||||
.string()
|
||||
.regex(
|
||||
/^[a-z][a-z0-9-]*\.[a-z][a-z0-9-]*$/,
|
||||
'ID skriptu musí mít tvar konektor.operace, například idoklad.get-issued-invoice.',
|
||||
'ID skriptu musí mít tvar sluzba.operace, například idoklad.get-issued-invoice.',
|
||||
),
|
||||
name: z.string().min(1, 'Název skriptu nesmí být prázdný.'),
|
||||
description: z.string().min(1, 'Popis skriptu nesmí být prázdný.'),
|
||||
|
||||
Reference in New Issue
Block a user