@tangle-network/agent-integrations 0.53.28 → 0.53.30
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/dist/catalog.js +4 -4
- package/dist/{chunk-UC7J6GZF.js → chunk-5LDZLVKP.js} +1462 -66
- package/dist/{chunk-UC7J6GZF.js.map → chunk-5LDZLVKP.js.map} +1 -1
- package/dist/{chunk-M2RUUX2L.js → chunk-DQIIDGA2.js} +3 -3
- package/dist/{chunk-IYZ3CXGS.js → chunk-DSIV6RU6.js} +2 -2
- package/dist/{chunk-USEBPDNS.js → chunk-UEJFZZ2E.js} +56 -2
- package/dist/chunk-UEJFZZ2E.js.map +1 -0
- package/dist/connectors/adapters/index.d.ts +65 -1
- package/dist/connectors/adapters/index.js +17 -1
- package/dist/connectors/index.d.ts +3 -1
- package/dist/connectors/index.js +18 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +20 -4
- package/dist/mcp.js +4 -4
- package/dist/registry.js +4 -4
- package/dist/runtime.js +4 -4
- package/dist/specs.js +3 -3
- package/docs/integration-execution-audit.md +13 -8
- package/docs/integration-execution-matrix.json +77 -72
- package/package.json +12 -2
- package/dist/chunk-USEBPDNS.js.map +0 -1
- /package/dist/{chunk-M2RUUX2L.js.map → chunk-DQIIDGA2.js.map} +0 -0
- /package/dist/{chunk-IYZ3CXGS.js.map → chunk-DSIV6RU6.js.map} +0 -0
|
@@ -181,10 +181,13 @@ __export(adapters_exports, {
|
|
|
181
181
|
copperConnector: () => copperConnector,
|
|
182
182
|
copyAiConnector: () => copyAiConnector,
|
|
183
183
|
couchbaseConnector: () => couchbaseConnector,
|
|
184
|
+
createKafkaConnector: () => createKafkaConnector,
|
|
185
|
+
createSftpConnector: () => createSftpConnector,
|
|
184
186
|
createTangleIdentityClient: () => createTangleIdentityClient,
|
|
185
187
|
crispConnector: () => crispConnector,
|
|
186
188
|
crustdataConnector: () => crustdataConnector,
|
|
187
189
|
cryptolensConnector: () => cryptolensConnector,
|
|
190
|
+
csvFilesConnector: () => csvFilesConnector,
|
|
188
191
|
customerIoConnector: () => customerIoConnector,
|
|
189
192
|
customgptConnector: () => customgptConnector,
|
|
190
193
|
dappierConnector: () => dappierConnector,
|
|
@@ -230,6 +233,7 @@ __export(adapters_exports, {
|
|
|
230
233
|
etsyConnector: () => etsyConnector,
|
|
231
234
|
everhourConnector: () => everhourConnector,
|
|
232
235
|
exaConnector: () => exaConnector,
|
|
236
|
+
excelFilesConnector: () => excelFilesConnector,
|
|
233
237
|
facebookLeadsConnector: () => facebookLeadsConnector,
|
|
234
238
|
facebookPagesConnector: () => facebookPagesConnector,
|
|
235
239
|
factorsAiConnector: () => factorsAiConnector,
|
|
@@ -349,6 +353,7 @@ __export(adapters_exports, {
|
|
|
349
353
|
jotformConnector: () => jotformConnector,
|
|
350
354
|
justInvoiceConnector: () => justInvoiceConnector,
|
|
351
355
|
justcallConnector: () => justcallConnector,
|
|
356
|
+
kafkaConnector: () => kafkaConnector,
|
|
352
357
|
kallabotAiConnector: () => kallabotAiConnector,
|
|
353
358
|
kapsoConnector: () => kapsoConnector,
|
|
354
359
|
kimaiConnector: () => kimaiConnector,
|
|
@@ -466,6 +471,7 @@ __export(adapters_exports, {
|
|
|
466
471
|
pagerdutyConnector: () => pagerdutyConnector,
|
|
467
472
|
pandadoc: () => pandadoc,
|
|
468
473
|
paperformConnector: () => paperformConnector,
|
|
474
|
+
parquetFilesConnector: () => parquetFilesConnector,
|
|
469
475
|
parseurConnector: () => parseurConnector,
|
|
470
476
|
pastefyConnector: () => pastefyConnector,
|
|
471
477
|
paychexConnector: () => paychexConnector,
|
|
@@ -494,6 +500,7 @@ __export(adapters_exports, {
|
|
|
494
500
|
predictLeadsConnector: () => predictLeadsConnector,
|
|
495
501
|
productboardConnector: () => productboardConnector,
|
|
496
502
|
proxycurlConnector: () => proxycurlConnector,
|
|
503
|
+
publicKafkaSocketFactory: () => publicKafkaSocketFactory,
|
|
497
504
|
pushbulletConnector: () => pushbulletConnector,
|
|
498
505
|
pushoverConnector: () => pushoverConnector,
|
|
499
506
|
qawafelConnector: () => qawafelConnector,
|
|
@@ -542,6 +549,7 @@ __export(adapters_exports, {
|
|
|
542
549
|
serperConnector: () => serperConnector,
|
|
543
550
|
serpstatConnector: () => serpstatConnector,
|
|
544
551
|
serviceNowConnector: () => serviceNowConnector,
|
|
552
|
+
sftpConnector: () => sftpConnector,
|
|
545
553
|
sharepoint: () => sharepoint,
|
|
546
554
|
shippoConnector: () => shippoConnector,
|
|
547
555
|
shopifyConnector: () => shopifyConnector,
|
|
@@ -4446,6 +4454,233 @@ var cartloomConnector = declarativeRestConnector({
|
|
|
4446
4454
|
]
|
|
4447
4455
|
});
|
|
4448
4456
|
|
|
4457
|
+
// src/connectors/adapters/csv-files.ts
|
|
4458
|
+
import { parse } from "csv-parse/sync";
|
|
4459
|
+
import { stringify } from "csv-stringify/sync";
|
|
4460
|
+
|
|
4461
|
+
// src/connectors/adapters/file-payload.ts
|
|
4462
|
+
var MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
4463
|
+
var MAX_RECORDS = 1e4;
|
|
4464
|
+
function readBase64File(value, label = "fileBase64") {
|
|
4465
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
4466
|
+
throw new Error(`${label} is required`);
|
|
4467
|
+
}
|
|
4468
|
+
if (value.length % 4 !== 0 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)) {
|
|
4469
|
+
throw new Error(`${label} must be canonical base64`);
|
|
4470
|
+
}
|
|
4471
|
+
const file = Buffer.from(value, "base64");
|
|
4472
|
+
if (file.byteLength > MAX_FILE_BYTES) {
|
|
4473
|
+
throw new Error(`${label} exceeds the ${MAX_FILE_BYTES}-byte limit`);
|
|
4474
|
+
}
|
|
4475
|
+
return file;
|
|
4476
|
+
}
|
|
4477
|
+
function encodeFile(file) {
|
|
4478
|
+
if (file.byteLength > MAX_FILE_BYTES) {
|
|
4479
|
+
throw new Error(`generated file exceeds the ${MAX_FILE_BYTES}-byte limit`);
|
|
4480
|
+
}
|
|
4481
|
+
return {
|
|
4482
|
+
fileBase64: Buffer.from(file).toString("base64"),
|
|
4483
|
+
byteLength: file.byteLength
|
|
4484
|
+
};
|
|
4485
|
+
}
|
|
4486
|
+
function readRecords(value) {
|
|
4487
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
4488
|
+
throw new Error("records must be a non-empty array of objects");
|
|
4489
|
+
}
|
|
4490
|
+
if (value.length > MAX_RECORDS) {
|
|
4491
|
+
throw new Error(`records exceeds the ${MAX_RECORDS}-record limit`);
|
|
4492
|
+
}
|
|
4493
|
+
assertStructuredInputSize(value, "records");
|
|
4494
|
+
return value.map((entry, index) => {
|
|
4495
|
+
if (!isPlainRecord(entry)) {
|
|
4496
|
+
throw new Error(`records[${index}] must be an object`);
|
|
4497
|
+
}
|
|
4498
|
+
return entry;
|
|
4499
|
+
});
|
|
4500
|
+
}
|
|
4501
|
+
function readRows(value) {
|
|
4502
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
4503
|
+
throw new Error("rows must be a non-empty array of arrays");
|
|
4504
|
+
}
|
|
4505
|
+
if (value.length > MAX_RECORDS) {
|
|
4506
|
+
throw new Error(`rows exceeds the ${MAX_RECORDS}-row limit`);
|
|
4507
|
+
}
|
|
4508
|
+
assertStructuredInputSize(value, "rows");
|
|
4509
|
+
return value.map((entry, index) => {
|
|
4510
|
+
if (!Array.isArray(entry)) throw new Error(`rows[${index}] must be an array`);
|
|
4511
|
+
return entry;
|
|
4512
|
+
});
|
|
4513
|
+
}
|
|
4514
|
+
function readOptionalString(value, label) {
|
|
4515
|
+
if (value === void 0 || value === null) return void 0;
|
|
4516
|
+
if (typeof value !== "string" || value.length === 0) throw new Error(`${label} must be a non-empty string`);
|
|
4517
|
+
return value;
|
|
4518
|
+
}
|
|
4519
|
+
function readBoolean(value, fallback, label) {
|
|
4520
|
+
if (value === void 0 || value === null) return fallback;
|
|
4521
|
+
if (typeof value !== "boolean") throw new Error(`${label} must be a boolean`);
|
|
4522
|
+
return value;
|
|
4523
|
+
}
|
|
4524
|
+
function readBoundedInteger(value, fallback, minimum, maximum, label) {
|
|
4525
|
+
if (value === void 0 || value === null) return fallback;
|
|
4526
|
+
if (!Number.isInteger(value) || value < minimum || value > maximum) {
|
|
4527
|
+
throw new Error(`${label} must be an integer from ${minimum} through ${maximum}`);
|
|
4528
|
+
}
|
|
4529
|
+
return value;
|
|
4530
|
+
}
|
|
4531
|
+
function jsonSafe(value) {
|
|
4532
|
+
if (typeof value === "bigint") return value.toString();
|
|
4533
|
+
if (value instanceof Date) return value.toISOString();
|
|
4534
|
+
if (value instanceof Uint8Array) return Buffer.from(value).toString("base64");
|
|
4535
|
+
if (Array.isArray(value)) return value.map(jsonSafe);
|
|
4536
|
+
if (isPlainRecord(value)) {
|
|
4537
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, jsonSafe(entry)]));
|
|
4538
|
+
}
|
|
4539
|
+
return value;
|
|
4540
|
+
}
|
|
4541
|
+
function isPlainRecord(value) {
|
|
4542
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
4543
|
+
}
|
|
4544
|
+
function assertStructuredInputSize(value, label) {
|
|
4545
|
+
let serialized;
|
|
4546
|
+
try {
|
|
4547
|
+
serialized = JSON.stringify(value, (_key, entry) => typeof entry === "bigint" ? entry.toString() : entry);
|
|
4548
|
+
} catch {
|
|
4549
|
+
throw new Error(`${label} must be JSON-serializable`);
|
|
4550
|
+
}
|
|
4551
|
+
if (Buffer.byteLength(serialized, "utf8") > MAX_FILE_BYTES) {
|
|
4552
|
+
throw new Error(`${label} exceeds the ${MAX_FILE_BYTES}-byte input limit`);
|
|
4553
|
+
}
|
|
4554
|
+
}
|
|
4555
|
+
|
|
4556
|
+
// src/connectors/adapters/csv-files.ts
|
|
4557
|
+
var MAX_COLUMNS = 256;
|
|
4558
|
+
var MAX_RECORD_SIZE = 1024 * 1024;
|
|
4559
|
+
var csvFilesConnector = {
|
|
4560
|
+
manifest: {
|
|
4561
|
+
kind: "csv-files",
|
|
4562
|
+
displayName: "CSV Files",
|
|
4563
|
+
description: "Parse bounded CSV files into records and create CSV files from structured records.",
|
|
4564
|
+
auth: { kind: "none" },
|
|
4565
|
+
defaultConsistencyModel: "advisory",
|
|
4566
|
+
category: "database",
|
|
4567
|
+
capabilities: [
|
|
4568
|
+
{
|
|
4569
|
+
name: "csv.parse",
|
|
4570
|
+
class: "read",
|
|
4571
|
+
description: "Parse a base64-encoded CSV file into object records or row arrays.",
|
|
4572
|
+
parameters: {
|
|
4573
|
+
type: "object",
|
|
4574
|
+
properties: {
|
|
4575
|
+
fileBase64: { type: "string", description: "CSV file content encoded as canonical base64." },
|
|
4576
|
+
delimiter: { type: "string", minLength: 1, maxLength: 1, default: "," },
|
|
4577
|
+
header: { type: "boolean", default: true }
|
|
4578
|
+
},
|
|
4579
|
+
required: ["fileBase64"],
|
|
4580
|
+
additionalProperties: false
|
|
4581
|
+
}
|
|
4582
|
+
},
|
|
4583
|
+
{
|
|
4584
|
+
name: "csv.create",
|
|
4585
|
+
class: "mutation",
|
|
4586
|
+
description: "Create a base64-encoded CSV file from object records.",
|
|
4587
|
+
parameters: {
|
|
4588
|
+
type: "object",
|
|
4589
|
+
properties: {
|
|
4590
|
+
records: { type: "array", minItems: 1, maxItems: MAX_RECORDS, items: { type: "object" } },
|
|
4591
|
+
columns: { type: "array", maxItems: MAX_COLUMNS, items: { type: "string", minLength: 1 } },
|
|
4592
|
+
delimiter: { type: "string", minLength: 1, maxLength: 1, default: "," },
|
|
4593
|
+
header: { type: "boolean", default: true }
|
|
4594
|
+
},
|
|
4595
|
+
required: ["records"],
|
|
4596
|
+
additionalProperties: false
|
|
4597
|
+
},
|
|
4598
|
+
cas: "none",
|
|
4599
|
+
externalEffect: true
|
|
4600
|
+
}
|
|
4601
|
+
]
|
|
4602
|
+
},
|
|
4603
|
+
async executeRead({ capabilityName, args }) {
|
|
4604
|
+
if (capabilityName !== "csv.parse") throw new Error(`Unknown CSV capability: ${capabilityName}`);
|
|
4605
|
+
const file = readBase64File(args.fileBase64);
|
|
4606
|
+
const delimiter = readDelimiter(args.delimiter);
|
|
4607
|
+
const header2 = readBoolean(args.header, true, "header");
|
|
4608
|
+
let recordCount = 0;
|
|
4609
|
+
const records = parse(file, {
|
|
4610
|
+
bom: true,
|
|
4611
|
+
columns: header2,
|
|
4612
|
+
delimiter,
|
|
4613
|
+
max_record_size: MAX_RECORD_SIZE,
|
|
4614
|
+
skip_empty_lines: true,
|
|
4615
|
+
on_record(record) {
|
|
4616
|
+
recordCount += 1;
|
|
4617
|
+
if (recordCount > MAX_RECORDS) throw new Error(`CSV exceeds the ${MAX_RECORDS}-record limit`);
|
|
4618
|
+
return record;
|
|
4619
|
+
}
|
|
4620
|
+
});
|
|
4621
|
+
if (records.some(
|
|
4622
|
+
(record) => header2 ? Object.keys(record).length > MAX_COLUMNS : Array.isArray(record) && record.length > MAX_COLUMNS
|
|
4623
|
+
)) {
|
|
4624
|
+
throw new Error(`CSV exceeds the ${MAX_COLUMNS}-column limit`);
|
|
4625
|
+
}
|
|
4626
|
+
return {
|
|
4627
|
+
data: { records, recordCount: records.length, header: header2 },
|
|
4628
|
+
fetchedAt: Date.now()
|
|
4629
|
+
};
|
|
4630
|
+
},
|
|
4631
|
+
async executeMutation({ capabilityName, args }) {
|
|
4632
|
+
if (capabilityName !== "csv.create") throw new Error(`Unknown CSV capability: ${capabilityName}`);
|
|
4633
|
+
const records = readRecords(args.records);
|
|
4634
|
+
const columns = readColumns(args.columns, records);
|
|
4635
|
+
const delimiter = readDelimiter(args.delimiter);
|
|
4636
|
+
const header2 = readBoolean(args.header, true, "header");
|
|
4637
|
+
const csv = stringify(
|
|
4638
|
+
records.map((record) => Object.fromEntries(columns.map((column) => [column, csvValue(record[column])]))),
|
|
4639
|
+
{ columns, delimiter, header: header2 }
|
|
4640
|
+
);
|
|
4641
|
+
return {
|
|
4642
|
+
status: "committed",
|
|
4643
|
+
data: { ...encodeFile(Buffer.from(csv, "utf8")), text: csv, columns, recordCount: records.length },
|
|
4644
|
+
committedAt: Date.now(),
|
|
4645
|
+
idempotentReplay: false
|
|
4646
|
+
};
|
|
4647
|
+
},
|
|
4648
|
+
async test() {
|
|
4649
|
+
return { ok: true };
|
|
4650
|
+
}
|
|
4651
|
+
};
|
|
4652
|
+
function readDelimiter(value) {
|
|
4653
|
+
const delimiter = readOptionalString(value, "delimiter") ?? ",";
|
|
4654
|
+
if ([...delimiter].length !== 1 || delimiter === "\r" || delimiter === "\n" || delimiter === '"') {
|
|
4655
|
+
throw new Error("delimiter must be one non-newline character other than a double quote");
|
|
4656
|
+
}
|
|
4657
|
+
return delimiter;
|
|
4658
|
+
}
|
|
4659
|
+
function readColumns(value, records) {
|
|
4660
|
+
const columns = value === void 0 ? [...new Set(records.flatMap((record) => Object.keys(record)))] : value;
|
|
4661
|
+
if (!Array.isArray(columns) || columns.length === 0 || columns.length > MAX_COLUMNS) {
|
|
4662
|
+
throw new Error(`columns must contain 1 through ${MAX_COLUMNS} names`);
|
|
4663
|
+
}
|
|
4664
|
+
const parsed = columns.map((column, index) => {
|
|
4665
|
+
if (typeof column !== "string" || column.length === 0) throw new Error(`columns[${index}] must be a non-empty string`);
|
|
4666
|
+
return column;
|
|
4667
|
+
});
|
|
4668
|
+
if (new Set(parsed).size !== parsed.length) throw new Error("columns must not contain duplicates");
|
|
4669
|
+
return parsed;
|
|
4670
|
+
}
|
|
4671
|
+
function csvValue(value) {
|
|
4672
|
+
if (value === null || value === void 0) return null;
|
|
4673
|
+
if (typeof value === "string") return value;
|
|
4674
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
4675
|
+
if (typeof value === "number") {
|
|
4676
|
+
if (!Number.isFinite(value)) throw new Error("CSV numeric values must be finite");
|
|
4677
|
+
return value;
|
|
4678
|
+
}
|
|
4679
|
+
if (typeof value === "bigint") return value.toString();
|
|
4680
|
+
if (value instanceof Date) return value.toISOString();
|
|
4681
|
+
return JSON.stringify(value);
|
|
4682
|
+
}
|
|
4683
|
+
|
|
4449
4684
|
// src/connectors/adapters/chain-aware.ts
|
|
4450
4685
|
var chainAwareConnector = declarativeRestConnector({
|
|
4451
4686
|
kind: "chain-aware",
|
|
@@ -6679,6 +6914,175 @@ var elevenlabsConnector = declarativeRestConnector({
|
|
|
6679
6914
|
]
|
|
6680
6915
|
});
|
|
6681
6916
|
|
|
6917
|
+
// src/connectors/adapters/excel-files.ts
|
|
6918
|
+
import readXlsxFile from "read-excel-file/node";
|
|
6919
|
+
import writeXlsxFile from "write-excel-file/node";
|
|
6920
|
+
var MAX_COLUMNS2 = 256;
|
|
6921
|
+
var MAX_SHEETS = 50;
|
|
6922
|
+
var MAX_UNCOMPRESSED_WORKBOOK_BYTES = 50 * 1024 * 1024;
|
|
6923
|
+
var MAX_ZIP_ENTRIES = 1e3;
|
|
6924
|
+
var excelFilesConnector = {
|
|
6925
|
+
manifest: {
|
|
6926
|
+
kind: "excel-files",
|
|
6927
|
+
displayName: "Excel Files",
|
|
6928
|
+
description: "Read bounded XLSX workbooks and create XLSX files from structured rows.",
|
|
6929
|
+
auth: { kind: "none" },
|
|
6930
|
+
defaultConsistencyModel: "advisory",
|
|
6931
|
+
category: "spreadsheet",
|
|
6932
|
+
capabilities: [
|
|
6933
|
+
{
|
|
6934
|
+
name: "excel.read",
|
|
6935
|
+
class: "read",
|
|
6936
|
+
description: "Read every sheet, or one named sheet, from a base64-encoded XLSX workbook.",
|
|
6937
|
+
parameters: {
|
|
6938
|
+
type: "object",
|
|
6939
|
+
properties: {
|
|
6940
|
+
fileBase64: { type: "string", description: "XLSX workbook content encoded as canonical base64." },
|
|
6941
|
+
sheet: {
|
|
6942
|
+
description: "Optional sheet name or one-based sheet number.",
|
|
6943
|
+
oneOf: [{ type: "string", minLength: 1 }, { type: "integer", minimum: 1 }]
|
|
6944
|
+
}
|
|
6945
|
+
},
|
|
6946
|
+
required: ["fileBase64"],
|
|
6947
|
+
additionalProperties: false
|
|
6948
|
+
}
|
|
6949
|
+
},
|
|
6950
|
+
{
|
|
6951
|
+
name: "excel.create",
|
|
6952
|
+
class: "mutation",
|
|
6953
|
+
description: "Create a single-sheet base64-encoded XLSX workbook from row arrays.",
|
|
6954
|
+
parameters: {
|
|
6955
|
+
type: "object",
|
|
6956
|
+
properties: {
|
|
6957
|
+
rows: {
|
|
6958
|
+
type: "array",
|
|
6959
|
+
minItems: 1,
|
|
6960
|
+
maxItems: MAX_RECORDS,
|
|
6961
|
+
items: { type: "array", maxItems: MAX_COLUMNS2 }
|
|
6962
|
+
},
|
|
6963
|
+
sheetName: { type: "string", minLength: 1, maxLength: 31, default: "Sheet1" }
|
|
6964
|
+
},
|
|
6965
|
+
required: ["rows"],
|
|
6966
|
+
additionalProperties: false
|
|
6967
|
+
},
|
|
6968
|
+
cas: "none",
|
|
6969
|
+
externalEffect: true
|
|
6970
|
+
}
|
|
6971
|
+
]
|
|
6972
|
+
},
|
|
6973
|
+
async executeRead({ capabilityName, args }) {
|
|
6974
|
+
if (capabilityName !== "excel.read") throw new Error(`Unknown Excel capability: ${capabilityName}`);
|
|
6975
|
+
const file = readBase64File(args.fileBase64);
|
|
6976
|
+
assertBoundedXlsxArchive(file);
|
|
6977
|
+
const requestedSheet = readSheetSelector(args.sheet);
|
|
6978
|
+
const workbook = await readXlsxFile(file);
|
|
6979
|
+
if (workbook.length > MAX_SHEETS) throw new Error(`workbook exceeds the ${MAX_SHEETS}-sheet limit`);
|
|
6980
|
+
const selected = requestedSheet === void 0 ? workbook : workbook.filter(
|
|
6981
|
+
(sheet, index) => typeof requestedSheet === "number" ? index + 1 === requestedSheet : sheet.sheet === requestedSheet
|
|
6982
|
+
);
|
|
6983
|
+
if (requestedSheet !== void 0 && selected.length === 0) {
|
|
6984
|
+
throw new Error(`sheet not found: ${requestedSheet}`);
|
|
6985
|
+
}
|
|
6986
|
+
let totalRows = 0;
|
|
6987
|
+
const sheets = selected.map((sheet) => {
|
|
6988
|
+
totalRows += sheet.data.length;
|
|
6989
|
+
if (totalRows > MAX_RECORDS) throw new Error(`workbook exceeds the ${MAX_RECORDS}-row limit`);
|
|
6990
|
+
for (const [rowIndex, row] of sheet.data.entries()) {
|
|
6991
|
+
if (row.length > MAX_COLUMNS2) {
|
|
6992
|
+
throw new Error(`sheet ${sheet.sheet} row ${rowIndex + 1} exceeds the ${MAX_COLUMNS2}-column limit`);
|
|
6993
|
+
}
|
|
6994
|
+
}
|
|
6995
|
+
return { name: sheet.sheet, rows: jsonSafe(sheet.data) };
|
|
6996
|
+
});
|
|
6997
|
+
return {
|
|
6998
|
+
data: { sheets, sheetCount: sheets.length, rowCount: totalRows },
|
|
6999
|
+
fetchedAt: Date.now()
|
|
7000
|
+
};
|
|
7001
|
+
},
|
|
7002
|
+
async executeMutation({ capabilityName, args }) {
|
|
7003
|
+
if (capabilityName !== "excel.create") throw new Error(`Unknown Excel capability: ${capabilityName}`);
|
|
7004
|
+
const rows = readRows(args.rows);
|
|
7005
|
+
for (const [index, row] of rows.entries()) {
|
|
7006
|
+
if (row.length > MAX_COLUMNS2) throw new Error(`rows[${index}] exceeds the ${MAX_COLUMNS2}-column limit`);
|
|
7007
|
+
}
|
|
7008
|
+
const sheetName = readOptionalString(args.sheetName, "sheetName") ?? "Sheet1";
|
|
7009
|
+
validateSheetName(sheetName);
|
|
7010
|
+
const sheetData = rows.map((row) => row.map(excelCell));
|
|
7011
|
+
const file = await writeXlsxFile(sheetData, { sheet: sheetName }).toBuffer();
|
|
7012
|
+
return {
|
|
7013
|
+
status: "committed",
|
|
7014
|
+
data: { ...encodeFile(file), sheetName, rowCount: rows.length },
|
|
7015
|
+
committedAt: Date.now(),
|
|
7016
|
+
idempotentReplay: false
|
|
7017
|
+
};
|
|
7018
|
+
},
|
|
7019
|
+
async test() {
|
|
7020
|
+
return { ok: true };
|
|
7021
|
+
}
|
|
7022
|
+
};
|
|
7023
|
+
function readSheetSelector(value) {
|
|
7024
|
+
if (value === void 0 || value === null) return void 0;
|
|
7025
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
7026
|
+
if (Number.isInteger(value) && value >= 1) return value;
|
|
7027
|
+
throw new Error("sheet must be a non-empty name or a one-based integer");
|
|
7028
|
+
}
|
|
7029
|
+
function validateSheetName(value) {
|
|
7030
|
+
if (value.length > 31 || /[\\/*?:[\]]/.test(value)) {
|
|
7031
|
+
throw new Error("sheetName must be at most 31 characters and contain no \\ / * ? : [ ] characters");
|
|
7032
|
+
}
|
|
7033
|
+
}
|
|
7034
|
+
function excelCell(value) {
|
|
7035
|
+
if (value === null || value === void 0) return null;
|
|
7036
|
+
if (typeof value === "string" || typeof value === "boolean" || value instanceof Date) return value;
|
|
7037
|
+
if (typeof value === "number") {
|
|
7038
|
+
if (!Number.isFinite(value)) throw new Error("Excel numeric values must be finite");
|
|
7039
|
+
return value;
|
|
7040
|
+
}
|
|
7041
|
+
if (typeof value === "bigint") return value.toString();
|
|
7042
|
+
return JSON.stringify(value);
|
|
7043
|
+
}
|
|
7044
|
+
function assertBoundedXlsxArchive(file) {
|
|
7045
|
+
const eocdSignature = 101010256;
|
|
7046
|
+
const centralEntrySignature = 33639248;
|
|
7047
|
+
if (file.byteLength < 22) throw new Error("XLSX file is missing its ZIP directory");
|
|
7048
|
+
const searchStart = Math.max(0, file.byteLength - 65557);
|
|
7049
|
+
let eocdOffset = -1;
|
|
7050
|
+
for (let offset2 = file.byteLength - 22; offset2 >= searchStart; offset2 -= 1) {
|
|
7051
|
+
if (file.readUInt32LE(offset2) === eocdSignature) {
|
|
7052
|
+
eocdOffset = offset2;
|
|
7053
|
+
break;
|
|
7054
|
+
}
|
|
7055
|
+
}
|
|
7056
|
+
if (eocdOffset < 0) throw new Error("XLSX file is missing its ZIP directory");
|
|
7057
|
+
const entryCount = file.readUInt16LE(eocdOffset + 10);
|
|
7058
|
+
const centralSize = file.readUInt32LE(eocdOffset + 12);
|
|
7059
|
+
const centralOffset = file.readUInt32LE(eocdOffset + 16);
|
|
7060
|
+
if (entryCount === 65535 || centralSize === 4294967295 || centralOffset === 4294967295) {
|
|
7061
|
+
throw new Error("ZIP64 XLSX workbooks are not supported");
|
|
7062
|
+
}
|
|
7063
|
+
if (entryCount === 0 || entryCount > MAX_ZIP_ENTRIES || centralOffset + centralSize > file.byteLength) {
|
|
7064
|
+
throw new Error("XLSX ZIP directory is invalid or too large");
|
|
7065
|
+
}
|
|
7066
|
+
let offset = centralOffset;
|
|
7067
|
+
let totalUncompressed = 0;
|
|
7068
|
+
for (let index = 0; index < entryCount; index += 1) {
|
|
7069
|
+
if (offset + 46 > file.byteLength || file.readUInt32LE(offset) !== centralEntrySignature) {
|
|
7070
|
+
throw new Error("XLSX ZIP directory entry is invalid");
|
|
7071
|
+
}
|
|
7072
|
+
const compressedSize = file.readUInt32LE(offset + 20);
|
|
7073
|
+
const uncompressedSize = file.readUInt32LE(offset + 24);
|
|
7074
|
+
const fileNameLength = file.readUInt16LE(offset + 28);
|
|
7075
|
+
const extraLength = file.readUInt16LE(offset + 30);
|
|
7076
|
+
const commentLength = file.readUInt16LE(offset + 32);
|
|
7077
|
+
totalUncompressed += uncompressedSize;
|
|
7078
|
+
if (totalUncompressed > MAX_UNCOMPRESSED_WORKBOOK_BYTES || uncompressedSize > 0 && compressedSize === 0) {
|
|
7079
|
+
throw new Error("XLSX workbook expands beyond the safe processing limit");
|
|
7080
|
+
}
|
|
7081
|
+
offset += 46 + fileNameLength + extraLength + commentLength;
|
|
7082
|
+
}
|
|
7083
|
+
if (offset > centralOffset + centralSize) throw new Error("XLSX ZIP directory length is invalid");
|
|
7084
|
+
}
|
|
7085
|
+
|
|
6682
7086
|
// src/connectors/adapters/everhour.ts
|
|
6683
7087
|
var everhourConnector = declarativeRestConnector({
|
|
6684
7088
|
kind: "everhour",
|
|
@@ -30336,7 +30740,7 @@ var httpConnector = {
|
|
|
30336
30740
|
});
|
|
30337
30741
|
const data4 = await parseResponse(res, method);
|
|
30338
30742
|
if (!res.ok) {
|
|
30339
|
-
throw new Error(`http ${method} ${url.toString()} failed: ${res.status} ${truncate(
|
|
30743
|
+
throw new Error(`http ${method} ${url.toString()} failed: ${res.status} ${truncate(stringify2(data4), 200)}`);
|
|
30340
30744
|
}
|
|
30341
30745
|
return {
|
|
30342
30746
|
data: data4,
|
|
@@ -30371,7 +30775,7 @@ var httpConnector = {
|
|
|
30371
30775
|
};
|
|
30372
30776
|
}
|
|
30373
30777
|
if (!res.ok) {
|
|
30374
|
-
throw new Error(`http ${method} ${url.toString()} failed: ${res.status} ${truncate(
|
|
30778
|
+
throw new Error(`http ${method} ${url.toString()} failed: ${res.status} ${truncate(stringify2(data4), 200)}`);
|
|
30375
30779
|
}
|
|
30376
30780
|
return {
|
|
30377
30781
|
status: "committed",
|
|
@@ -30501,7 +30905,7 @@ function parseRetryAfterMs(header2) {
|
|
|
30501
30905
|
if (!Number.isFinite(date)) return 1e3;
|
|
30502
30906
|
return Math.max(0, date - Date.now());
|
|
30503
30907
|
}
|
|
30504
|
-
function
|
|
30908
|
+
function stringify2(value) {
|
|
30505
30909
|
if (typeof value === "string") return value;
|
|
30506
30910
|
try {
|
|
30507
30911
|
return JSON.stringify(value);
|
|
@@ -32756,6 +33160,548 @@ var justcallConnector = declarativeRestConnector({
|
|
|
32756
33160
|
]
|
|
32757
33161
|
});
|
|
32758
33162
|
|
|
33163
|
+
// src/connectors/adapters/kafka.ts
|
|
33164
|
+
import { isIP as isIP2 } from "net";
|
|
33165
|
+
import tls from "tls";
|
|
33166
|
+
import {
|
|
33167
|
+
Kafka,
|
|
33168
|
+
logLevel
|
|
33169
|
+
} from "kafkajs";
|
|
33170
|
+
|
|
33171
|
+
// src/connectors/adapters/public-network.ts
|
|
33172
|
+
import { lookup } from "dns/promises";
|
|
33173
|
+
import ipaddr from "ipaddr.js";
|
|
33174
|
+
function isPublicNetworkAddress(value) {
|
|
33175
|
+
if (!ipaddr.isValid(value)) return false;
|
|
33176
|
+
let address = ipaddr.parse(value);
|
|
33177
|
+
if (address.kind() === "ipv6" && address.isIPv4MappedAddress()) {
|
|
33178
|
+
address = address.toIPv4Address();
|
|
33179
|
+
}
|
|
33180
|
+
return address.range() === "unicast";
|
|
33181
|
+
}
|
|
33182
|
+
async function resolvePublicHostAddresses(host) {
|
|
33183
|
+
const addresses = ipaddr.isValid(host) ? [host] : (await lookup(host, { all: true, verbatim: true })).map((entry) => entry.address);
|
|
33184
|
+
if (addresses.length === 0 || addresses.some((address) => !isPublicNetworkAddress(address))) {
|
|
33185
|
+
throw new Error("host is not a public network target");
|
|
33186
|
+
}
|
|
33187
|
+
return addresses;
|
|
33188
|
+
}
|
|
33189
|
+
var publicDnsLookup = (hostname, options, callback) => {
|
|
33190
|
+
const normalizedOptions = typeof options === "number" ? { family: options } : options;
|
|
33191
|
+
lookup(hostname, { ...normalizedOptions, all: true, verbatim: true }).then((entries) => {
|
|
33192
|
+
if (entries.length === 0 || entries.some((entry) => !isPublicNetworkAddress(entry.address))) {
|
|
33193
|
+
callback(new Error("host is not a public network target"), "", 0);
|
|
33194
|
+
return;
|
|
33195
|
+
}
|
|
33196
|
+
if (normalizedOptions?.all) {
|
|
33197
|
+
callback(null, entries, void 0);
|
|
33198
|
+
return;
|
|
33199
|
+
}
|
|
33200
|
+
callback(null, entries[0].address, entries[0].family);
|
|
33201
|
+
}).catch((error) => callback(error instanceof Error ? error : new Error("DNS lookup failed"), "", 0));
|
|
33202
|
+
};
|
|
33203
|
+
|
|
33204
|
+
// src/connectors/adapters/kafka.ts
|
|
33205
|
+
var MAX_TOPICS = 100;
|
|
33206
|
+
var MAX_MESSAGES = 100;
|
|
33207
|
+
var MAX_MESSAGE_BYTES2 = 1024 * 1024;
|
|
33208
|
+
var MAX_BATCH_BYTES2 = MAX_FILE_BYTES;
|
|
33209
|
+
var MAX_HEADERS = 64;
|
|
33210
|
+
var MAX_POLL_MS = 3e4;
|
|
33211
|
+
function createKafkaConnector(options = {}) {
|
|
33212
|
+
const createClient = options.createClient ?? ((config) => new Kafka(config));
|
|
33213
|
+
return {
|
|
33214
|
+
manifest: {
|
|
33215
|
+
kind: "kafka",
|
|
33216
|
+
displayName: "Apache Kafka",
|
|
33217
|
+
description: "Discover topics and groups, inspect offsets, produce records, and run bounded approved consumers over TLS.",
|
|
33218
|
+
auth: {
|
|
33219
|
+
kind: "api-key",
|
|
33220
|
+
hint: "JSON with brokers and optional SASL or mutual-TLS credentials. TLS is mandatory and broker addresses must resolve publicly."
|
|
33221
|
+
},
|
|
33222
|
+
defaultConsistencyModel: "advisory",
|
|
33223
|
+
category: "webhook",
|
|
33224
|
+
rateLimit: { requests: 120, windowMs: 6e4, scope: "data-source" },
|
|
33225
|
+
capabilities: [
|
|
33226
|
+
readCapability5("kafka.topics.list", "List Kafka topics visible to the connection.", emptySchema()),
|
|
33227
|
+
readCapability5("kafka.topics.describe", "Read partition and replica metadata for selected topics.", {
|
|
33228
|
+
type: "object",
|
|
33229
|
+
properties: { topics: topicArraySchema() },
|
|
33230
|
+
required: ["topics"],
|
|
33231
|
+
additionalProperties: false
|
|
33232
|
+
}),
|
|
33233
|
+
readCapability5("kafka.offsets.list", "Read low, high, and current offsets for one topic.", topicSchema()),
|
|
33234
|
+
readCapability5("kafka.groups.list", "List consumer groups visible to the connection.", emptySchema()),
|
|
33235
|
+
readCapability5("kafka.group.offsets", "Read committed offsets for a consumer group.", {
|
|
33236
|
+
type: "object",
|
|
33237
|
+
properties: {
|
|
33238
|
+
groupId: { type: "string", minLength: 1, maxLength: 255 },
|
|
33239
|
+
topics: topicArraySchema()
|
|
33240
|
+
},
|
|
33241
|
+
required: ["groupId"],
|
|
33242
|
+
additionalProperties: false
|
|
33243
|
+
}),
|
|
33244
|
+
mutationCapability5("kafka.messages.produce", "Produce one or more records with all acknowledgements enabled.", {
|
|
33245
|
+
type: "object",
|
|
33246
|
+
properties: {
|
|
33247
|
+
topic: topicNameSchema(),
|
|
33248
|
+
encoding: { type: "string", enum: ["utf8", "base64"], default: "utf8" },
|
|
33249
|
+
messages: {
|
|
33250
|
+
type: "array",
|
|
33251
|
+
minItems: 1,
|
|
33252
|
+
maxItems: MAX_MESSAGES,
|
|
33253
|
+
items: {
|
|
33254
|
+
type: "object",
|
|
33255
|
+
properties: {
|
|
33256
|
+
value: { type: "string" },
|
|
33257
|
+
key: { type: "string" },
|
|
33258
|
+
partition: { type: "integer", minimum: 0 },
|
|
33259
|
+
headers: { type: "object", additionalProperties: { type: "string" }, maxProperties: MAX_HEADERS }
|
|
33260
|
+
},
|
|
33261
|
+
required: ["value"],
|
|
33262
|
+
additionalProperties: false
|
|
33263
|
+
}
|
|
33264
|
+
}
|
|
33265
|
+
},
|
|
33266
|
+
required: ["topic", "messages"],
|
|
33267
|
+
additionalProperties: false
|
|
33268
|
+
}),
|
|
33269
|
+
mutationCapability5("kafka.messages.consume", "Join a consumer group and return a bounded batch without automatically committing offsets.", {
|
|
33270
|
+
type: "object",
|
|
33271
|
+
properties: {
|
|
33272
|
+
topic: topicNameSchema(),
|
|
33273
|
+
groupId: { type: "string", minLength: 1, maxLength: 255 },
|
|
33274
|
+
encoding: { type: "string", enum: ["utf8", "base64"], default: "utf8" },
|
|
33275
|
+
fromBeginning: { type: "boolean", default: false },
|
|
33276
|
+
limit: { type: "integer", minimum: 1, maximum: MAX_MESSAGES, default: 25 },
|
|
33277
|
+
timeoutMs: { type: "integer", minimum: 250, maximum: MAX_POLL_MS, default: 5e3 }
|
|
33278
|
+
},
|
|
33279
|
+
required: ["topic", "groupId"],
|
|
33280
|
+
additionalProperties: false
|
|
33281
|
+
}),
|
|
33282
|
+
mutationCapability5("kafka.offsets.commit", "Commit explicit next offsets for a consumer group.", {
|
|
33283
|
+
type: "object",
|
|
33284
|
+
properties: {
|
|
33285
|
+
groupId: { type: "string", minLength: 1, maxLength: 255 },
|
|
33286
|
+
offsets: {
|
|
33287
|
+
type: "array",
|
|
33288
|
+
minItems: 1,
|
|
33289
|
+
maxItems: MAX_MESSAGES,
|
|
33290
|
+
items: {
|
|
33291
|
+
type: "object",
|
|
33292
|
+
properties: {
|
|
33293
|
+
topic: topicNameSchema(),
|
|
33294
|
+
partition: { type: "integer", minimum: 0 },
|
|
33295
|
+
offset: { type: "string", pattern: "^[0-9]+$" },
|
|
33296
|
+
metadata: { type: "string", maxLength: 4096 }
|
|
33297
|
+
},
|
|
33298
|
+
required: ["topic", "partition", "offset"],
|
|
33299
|
+
additionalProperties: false
|
|
33300
|
+
}
|
|
33301
|
+
}
|
|
33302
|
+
},
|
|
33303
|
+
required: ["groupId", "offsets"],
|
|
33304
|
+
additionalProperties: false
|
|
33305
|
+
}),
|
|
33306
|
+
mutationCapability5("kafka.topics.create", "Create one Kafka topic with explicit partition and replication counts.", {
|
|
33307
|
+
type: "object",
|
|
33308
|
+
properties: {
|
|
33309
|
+
topic: topicNameSchema(),
|
|
33310
|
+
partitions: { type: "integer", minimum: 1, maximum: 100, default: 1 },
|
|
33311
|
+
replicationFactor: { type: "integer", minimum: 1, maximum: 10, default: 1 }
|
|
33312
|
+
},
|
|
33313
|
+
required: ["topic"],
|
|
33314
|
+
additionalProperties: false
|
|
33315
|
+
}),
|
|
33316
|
+
mutationCapability5("kafka.topics.delete", "Delete one or more Kafka topics.", {
|
|
33317
|
+
type: "object",
|
|
33318
|
+
properties: { topics: topicArraySchema() },
|
|
33319
|
+
required: ["topics"],
|
|
33320
|
+
additionalProperties: false
|
|
33321
|
+
})
|
|
33322
|
+
]
|
|
33323
|
+
},
|
|
33324
|
+
async executeRead({ source, capabilityName, args }) {
|
|
33325
|
+
const client = createClient(readCredentials(source).config);
|
|
33326
|
+
return withAdmin(client.admin(), async (admin) => {
|
|
33327
|
+
let data4;
|
|
33328
|
+
if (capabilityName === "kafka.topics.list") {
|
|
33329
|
+
data4 = { topics: await admin.listTopics() };
|
|
33330
|
+
} else if (capabilityName === "kafka.topics.describe") {
|
|
33331
|
+
data4 = await admin.fetchTopicMetadata({ topics: readTopics(args.topics) });
|
|
33332
|
+
} else if (capabilityName === "kafka.offsets.list") {
|
|
33333
|
+
const topic = readTopic(args.topic);
|
|
33334
|
+
data4 = { topic, partitions: await admin.fetchTopicOffsets(topic) };
|
|
33335
|
+
} else if (capabilityName === "kafka.groups.list") {
|
|
33336
|
+
data4 = await admin.listGroups();
|
|
33337
|
+
} else if (capabilityName === "kafka.group.offsets") {
|
|
33338
|
+
const groupId5 = readGroupId2(args.groupId);
|
|
33339
|
+
const topics = args.topics === void 0 ? void 0 : readTopics(args.topics);
|
|
33340
|
+
data4 = { groupId: groupId5, offsets: await admin.fetchOffsets({ groupId: groupId5, topics, resolveOffsets: true }) };
|
|
33341
|
+
} else {
|
|
33342
|
+
throw new Error(`Unknown Kafka read capability: ${capabilityName}`);
|
|
33343
|
+
}
|
|
33344
|
+
return { data: jsonSafe(data4), fetchedAt: Date.now() };
|
|
33345
|
+
});
|
|
33346
|
+
},
|
|
33347
|
+
async executeMutation({ source, capabilityName, args }) {
|
|
33348
|
+
const client = createClient(readCredentials(source).config);
|
|
33349
|
+
let data4;
|
|
33350
|
+
if (capabilityName === "kafka.messages.produce") {
|
|
33351
|
+
data4 = await produce(client.producer({ idempotent: true, maxInFlightRequests: 1 }), args);
|
|
33352
|
+
} else if (capabilityName === "kafka.messages.consume") {
|
|
33353
|
+
data4 = await consume(client.consumer({
|
|
33354
|
+
groupId: readGroupId2(args.groupId),
|
|
33355
|
+
allowAutoTopicCreation: false,
|
|
33356
|
+
maxBytes: MAX_BATCH_BYTES2,
|
|
33357
|
+
maxBytesPerPartition: MAX_BATCH_BYTES2
|
|
33358
|
+
}), args);
|
|
33359
|
+
} else if (capabilityName === "kafka.offsets.commit") {
|
|
33360
|
+
data4 = await commitOffsets(client.consumer({
|
|
33361
|
+
groupId: readGroupId2(args.groupId),
|
|
33362
|
+
allowAutoTopicCreation: false
|
|
33363
|
+
}), args);
|
|
33364
|
+
} else if (capabilityName === "kafka.topics.create") {
|
|
33365
|
+
data4 = await withAdmin(client.admin(), async (admin) => ({
|
|
33366
|
+
created: await admin.createTopics({
|
|
33367
|
+
waitForLeaders: true,
|
|
33368
|
+
topics: [{
|
|
33369
|
+
topic: readTopic(args.topic),
|
|
33370
|
+
numPartitions: readBoundedInteger(args.partitions, 1, 1, 100, "partitions"),
|
|
33371
|
+
replicationFactor: readBoundedInteger(args.replicationFactor, 1, 1, 10, "replicationFactor")
|
|
33372
|
+
}]
|
|
33373
|
+
})
|
|
33374
|
+
}));
|
|
33375
|
+
} else if (capabilityName === "kafka.topics.delete") {
|
|
33376
|
+
const topics = readTopics(args.topics);
|
|
33377
|
+
data4 = await withAdmin(client.admin(), async (admin) => {
|
|
33378
|
+
await admin.deleteTopics({ topics });
|
|
33379
|
+
return { topics };
|
|
33380
|
+
});
|
|
33381
|
+
} else {
|
|
33382
|
+
throw new Error(`Unknown Kafka mutation capability: ${capabilityName}`);
|
|
33383
|
+
}
|
|
33384
|
+
return {
|
|
33385
|
+
status: "committed",
|
|
33386
|
+
data: jsonSafe(data4),
|
|
33387
|
+
committedAt: Date.now(),
|
|
33388
|
+
idempotentReplay: false
|
|
33389
|
+
};
|
|
33390
|
+
},
|
|
33391
|
+
async test(source) {
|
|
33392
|
+
try {
|
|
33393
|
+
const admin = createClient(readCredentials(source).config).admin();
|
|
33394
|
+
await withAdmin(admin, (connected) => connected.listTopics());
|
|
33395
|
+
return { ok: true };
|
|
33396
|
+
} catch (error) {
|
|
33397
|
+
return { ok: false, reason: error instanceof Error ? error.message : "Kafka connection test failed" };
|
|
33398
|
+
}
|
|
33399
|
+
}
|
|
33400
|
+
};
|
|
33401
|
+
}
|
|
33402
|
+
var kafkaConnector = createKafkaConnector();
|
|
33403
|
+
var publicKafkaSocketFactory = ({ host, port, ssl, onConnect }) => {
|
|
33404
|
+
validateBrokerHost(host);
|
|
33405
|
+
if (isIP2(host) !== 0 && !isPublicNetworkAddress(host)) {
|
|
33406
|
+
throw new Error("Kafka broker is not a public network target");
|
|
33407
|
+
}
|
|
33408
|
+
const socket = tls.connect({
|
|
33409
|
+
...ssl,
|
|
33410
|
+
host,
|
|
33411
|
+
port,
|
|
33412
|
+
servername: isIP2(host) === 0 ? host : void 0,
|
|
33413
|
+
lookup: publicDnsLookup
|
|
33414
|
+
}, onConnect);
|
|
33415
|
+
socket.setKeepAlive(true, 6e4);
|
|
33416
|
+
return socket;
|
|
33417
|
+
};
|
|
33418
|
+
async function withAdmin(admin, run) {
|
|
33419
|
+
try {
|
|
33420
|
+
await admin.connect();
|
|
33421
|
+
return await run(admin);
|
|
33422
|
+
} finally {
|
|
33423
|
+
await admin.disconnect().catch(() => void 0);
|
|
33424
|
+
}
|
|
33425
|
+
}
|
|
33426
|
+
async function produce(producer, args) {
|
|
33427
|
+
const topic = readTopic(args.topic);
|
|
33428
|
+
const encoding = readEncoding(args.encoding);
|
|
33429
|
+
const messages = readMessageInputs(args.messages).map((message) => ({
|
|
33430
|
+
value: decodeMessageValue(message.value, encoding, "value"),
|
|
33431
|
+
key: message.key === void 0 ? void 0 : decodeMessageValue(message.key, encoding, "key"),
|
|
33432
|
+
partition: message.partition === void 0 ? void 0 : readBoundedInteger(message.partition, 0, 0, Number.MAX_SAFE_INTEGER, "partition"),
|
|
33433
|
+
headers: readHeaders(message.headers)
|
|
33434
|
+
}));
|
|
33435
|
+
const batchBytes = messages.reduce((total, message) => total + message.value.byteLength + (message.key?.byteLength ?? 0) + headerBytes(message.headers), 0);
|
|
33436
|
+
if (batchBytes > MAX_BATCH_BYTES2) throw new Error(`messages exceed the ${MAX_BATCH_BYTES2}-byte batch limit`);
|
|
33437
|
+
try {
|
|
33438
|
+
await producer.connect();
|
|
33439
|
+
return { topic, records: await producer.send({ topic, messages, acks: -1, timeout: 3e4 }) };
|
|
33440
|
+
} finally {
|
|
33441
|
+
await producer.disconnect().catch(() => void 0);
|
|
33442
|
+
}
|
|
33443
|
+
}
|
|
33444
|
+
async function consume(consumer, args) {
|
|
33445
|
+
const topic = readTopic(args.topic);
|
|
33446
|
+
const encoding = readEncoding(args.encoding);
|
|
33447
|
+
const limit = readBoundedInteger(args.limit, 25, 1, MAX_MESSAGES, "limit");
|
|
33448
|
+
const timeoutMs = readBoundedInteger(args.timeoutMs, 5e3, 250, MAX_POLL_MS, "timeoutMs");
|
|
33449
|
+
const messages = [];
|
|
33450
|
+
let batchBytes = 0;
|
|
33451
|
+
let resolveDone = () => void 0;
|
|
33452
|
+
let rejectDone = () => void 0;
|
|
33453
|
+
const done = new Promise((resolve, reject) => {
|
|
33454
|
+
resolveDone = resolve;
|
|
33455
|
+
rejectDone = reject;
|
|
33456
|
+
});
|
|
33457
|
+
let timer;
|
|
33458
|
+
try {
|
|
33459
|
+
await consumer.connect();
|
|
33460
|
+
await consumer.subscribe({ topics: [topic], fromBeginning: readBoolean(args.fromBeginning, false, "fromBeginning") });
|
|
33461
|
+
const run = consumer.run({
|
|
33462
|
+
autoCommit: false,
|
|
33463
|
+
partitionsConsumedConcurrently: 1,
|
|
33464
|
+
eachMessage: async ({ partition, message }) => {
|
|
33465
|
+
if (messages.length >= limit) return;
|
|
33466
|
+
const messageBytes = (message.key?.byteLength ?? 0) + (message.value?.byteLength ?? 0) + headerBytes(message.headers);
|
|
33467
|
+
if (messageBytes > MAX_MESSAGE_BYTES2) throw new Error(`Kafka message exceeds the ${MAX_MESSAGE_BYTES2}-byte limit`);
|
|
33468
|
+
batchBytes += messageBytes;
|
|
33469
|
+
if (batchBytes > MAX_BATCH_BYTES2) throw new Error(`Kafka batch exceeds the ${MAX_BATCH_BYTES2}-byte limit`);
|
|
33470
|
+
messages.push({
|
|
33471
|
+
topic,
|
|
33472
|
+
partition,
|
|
33473
|
+
offset: message.offset,
|
|
33474
|
+
timestamp: message.timestamp,
|
|
33475
|
+
key: encodeMessageValue(message.key, encoding),
|
|
33476
|
+
value: encodeMessageValue(message.value, encoding),
|
|
33477
|
+
headers: Object.fromEntries(Object.entries(message.headers ?? {}).map(([key, value]) => [key, encodeHeader(value, encoding)]))
|
|
33478
|
+
});
|
|
33479
|
+
if (messages.length >= limit) resolveDone();
|
|
33480
|
+
}
|
|
33481
|
+
});
|
|
33482
|
+
run.catch(rejectDone);
|
|
33483
|
+
timer = setTimeout(resolveDone, timeoutMs);
|
|
33484
|
+
await done;
|
|
33485
|
+
await consumer.stop();
|
|
33486
|
+
await run;
|
|
33487
|
+
return { topic, messages, messageCount: messages.length, offsetsCommitted: false };
|
|
33488
|
+
} finally {
|
|
33489
|
+
if (timer) clearTimeout(timer);
|
|
33490
|
+
await consumer.disconnect().catch(() => void 0);
|
|
33491
|
+
}
|
|
33492
|
+
}
|
|
33493
|
+
async function commitOffsets(consumer, args) {
|
|
33494
|
+
if (!Array.isArray(args.offsets) || args.offsets.length === 0 || args.offsets.length > MAX_MESSAGES) {
|
|
33495
|
+
throw new Error(`offsets must contain 1 through ${MAX_MESSAGES} entries`);
|
|
33496
|
+
}
|
|
33497
|
+
const offsets = args.offsets.map((entry, index) => {
|
|
33498
|
+
if (!isPlainRecord(entry)) throw new Error(`offsets[${index}] must be an object`);
|
|
33499
|
+
const offset = readOptionalString(entry.offset, `offsets[${index}].offset`);
|
|
33500
|
+
if (!offset || !/^[0-9]+$/.test(offset) || BigInt(offset) > 9223372036854775807n) {
|
|
33501
|
+
throw new Error(`offsets[${index}].offset must be a signed 64-bit non-negative integer string`);
|
|
33502
|
+
}
|
|
33503
|
+
return {
|
|
33504
|
+
topic: readTopic(entry.topic),
|
|
33505
|
+
partition: readBoundedInteger(entry.partition, 0, 0, Number.MAX_SAFE_INTEGER, `offsets[${index}].partition`),
|
|
33506
|
+
offset,
|
|
33507
|
+
metadata: readOptionalString(entry.metadata, `offsets[${index}].metadata`)
|
|
33508
|
+
};
|
|
33509
|
+
});
|
|
33510
|
+
try {
|
|
33511
|
+
await consumer.connect();
|
|
33512
|
+
await consumer.commitOffsets(offsets);
|
|
33513
|
+
return { groupId: readGroupId2(args.groupId), offsets };
|
|
33514
|
+
} finally {
|
|
33515
|
+
await consumer.disconnect().catch(() => void 0);
|
|
33516
|
+
}
|
|
33517
|
+
}
|
|
33518
|
+
function readCredentials(source) {
|
|
33519
|
+
let raw;
|
|
33520
|
+
if (source.credentials.kind === "custom") raw = source.credentials.values;
|
|
33521
|
+
else if (source.credentials.kind === "api-key") {
|
|
33522
|
+
try {
|
|
33523
|
+
raw = JSON.parse(source.credentials.apiKey);
|
|
33524
|
+
} catch {
|
|
33525
|
+
throw new Error("Kafka credential must be valid JSON");
|
|
33526
|
+
}
|
|
33527
|
+
} else {
|
|
33528
|
+
throw new Error("Kafka requires a structured credential bundle");
|
|
33529
|
+
}
|
|
33530
|
+
if (!isPlainRecord(raw)) throw new Error("Kafka credential must be a JSON object");
|
|
33531
|
+
const brokers = readBrokers(raw.brokers);
|
|
33532
|
+
const clientId = readOptionalString(raw.clientId, "clientId") ?? "tangle-integration-hub";
|
|
33533
|
+
if (!/^[A-Za-z0-9._-]{1,128}$/.test(clientId)) throw new Error("clientId contains unsupported characters");
|
|
33534
|
+
const sasl = readSasl(raw);
|
|
33535
|
+
const ca = readPemList(raw.tlsCa, "tlsCa");
|
|
33536
|
+
const cert = readOptionalString(raw.tlsCert, "tlsCert");
|
|
33537
|
+
const key = readOptionalString(raw.tlsKey, "tlsKey");
|
|
33538
|
+
if (Boolean(cert) !== Boolean(key)) throw new Error("tlsCert and tlsKey must be supplied together");
|
|
33539
|
+
return {
|
|
33540
|
+
config: {
|
|
33541
|
+
brokers,
|
|
33542
|
+
clientId,
|
|
33543
|
+
ssl: {
|
|
33544
|
+
minVersion: "TLSv1.2",
|
|
33545
|
+
rejectUnauthorized: true,
|
|
33546
|
+
ca,
|
|
33547
|
+
cert,
|
|
33548
|
+
key,
|
|
33549
|
+
passphrase: readOptionalString(raw.tlsPassphrase, "tlsPassphrase")
|
|
33550
|
+
},
|
|
33551
|
+
sasl,
|
|
33552
|
+
socketFactory: publicKafkaSocketFactory,
|
|
33553
|
+
connectionTimeout: 1e4,
|
|
33554
|
+
authenticationTimeout: 1e4,
|
|
33555
|
+
requestTimeout: 3e4,
|
|
33556
|
+
enforceRequestTimeout: true,
|
|
33557
|
+
retry: { retries: 3, maxRetryTime: 3e4 },
|
|
33558
|
+
logLevel: logLevel.NOTHING
|
|
33559
|
+
}
|
|
33560
|
+
};
|
|
33561
|
+
}
|
|
33562
|
+
function readBrokers(value) {
|
|
33563
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > 20) {
|
|
33564
|
+
throw new Error("brokers must contain 1 through 20 host:port entries");
|
|
33565
|
+
}
|
|
33566
|
+
return value.map((entry, index) => {
|
|
33567
|
+
if (typeof entry !== "string" || entry.length === 0) throw new Error(`brokers[${index}] must be a host:port string`);
|
|
33568
|
+
let parsed;
|
|
33569
|
+
try {
|
|
33570
|
+
parsed = new URL(`tcp://${entry}`);
|
|
33571
|
+
} catch {
|
|
33572
|
+
throw new Error(`brokers[${index}] is not a valid host:port`);
|
|
33573
|
+
}
|
|
33574
|
+
const host = parsed.hostname.replace(/^\[|\]$/g, "");
|
|
33575
|
+
if (!parsed.port || parsed.username || parsed.password || !["", "/"].includes(parsed.pathname)) {
|
|
33576
|
+
throw new Error(`brokers[${index}] must contain only host and port`);
|
|
33577
|
+
}
|
|
33578
|
+
validateBrokerHost(host);
|
|
33579
|
+
if (isIP2(host) !== 0 && !isPublicNetworkAddress(host)) throw new Error(`brokers[${index}] is not a public network target`);
|
|
33580
|
+
const port = Number(parsed.port);
|
|
33581
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`brokers[${index}] port is invalid`);
|
|
33582
|
+
return entry;
|
|
33583
|
+
});
|
|
33584
|
+
}
|
|
33585
|
+
function validateBrokerHost(host) {
|
|
33586
|
+
const normalized = host.toLowerCase();
|
|
33587
|
+
if (host.length === 0 || host.length > 253 || normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".local") || /[\s/@]/.test(host)) {
|
|
33588
|
+
throw new Error("Kafka broker must be a public hostname or IP address");
|
|
33589
|
+
}
|
|
33590
|
+
}
|
|
33591
|
+
function readSasl(raw) {
|
|
33592
|
+
if (raw.saslMechanism === void 0 || raw.saslMechanism === null) return void 0;
|
|
33593
|
+
if (!["plain", "scram-sha-256", "scram-sha-512"].includes(String(raw.saslMechanism))) {
|
|
33594
|
+
throw new Error("saslMechanism must be plain, scram-sha-256, or scram-sha-512");
|
|
33595
|
+
}
|
|
33596
|
+
const username = readOptionalString(raw.saslUsername, "saslUsername");
|
|
33597
|
+
const password = readOptionalString(raw.saslPassword, "saslPassword");
|
|
33598
|
+
if (!username || !password) throw new Error("SASL requires saslUsername and saslPassword");
|
|
33599
|
+
return { mechanism: raw.saslMechanism, username, password };
|
|
33600
|
+
}
|
|
33601
|
+
function readPemList(value, label) {
|
|
33602
|
+
if (value === void 0 || value === null) return void 0;
|
|
33603
|
+
const values = Array.isArray(value) ? value : [value];
|
|
33604
|
+
if (values.length === 0 || values.some((entry) => typeof entry !== "string" || entry.length === 0)) {
|
|
33605
|
+
throw new Error(`${label} must be a PEM string or non-empty array of PEM strings`);
|
|
33606
|
+
}
|
|
33607
|
+
return values;
|
|
33608
|
+
}
|
|
33609
|
+
function readTopics(value) {
|
|
33610
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > MAX_TOPICS) {
|
|
33611
|
+
throw new Error(`topics must contain 1 through ${MAX_TOPICS} names`);
|
|
33612
|
+
}
|
|
33613
|
+
const topics = value.map(readTopic);
|
|
33614
|
+
if (new Set(topics).size !== topics.length) throw new Error("topics must not contain duplicates");
|
|
33615
|
+
return topics;
|
|
33616
|
+
}
|
|
33617
|
+
function readTopic(value) {
|
|
33618
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9._-]{1,249}$/.test(value) || value === "." || value === "..") {
|
|
33619
|
+
throw new Error("topic must be 1 through 249 Kafka-safe characters");
|
|
33620
|
+
}
|
|
33621
|
+
return value;
|
|
33622
|
+
}
|
|
33623
|
+
function readGroupId2(value) {
|
|
33624
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 255 || /[\u0000-\u001f]/.test(value)) {
|
|
33625
|
+
throw new Error("groupId must be 1 through 255 printable characters");
|
|
33626
|
+
}
|
|
33627
|
+
return value;
|
|
33628
|
+
}
|
|
33629
|
+
function readEncoding(value) {
|
|
33630
|
+
if (value === void 0 || value === null || value === "utf8") return "utf8";
|
|
33631
|
+
if (value === "base64") return "base64";
|
|
33632
|
+
throw new Error("encoding must be utf8 or base64");
|
|
33633
|
+
}
|
|
33634
|
+
function readMessageInputs(value) {
|
|
33635
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > MAX_MESSAGES) {
|
|
33636
|
+
throw new Error(`messages must contain 1 through ${MAX_MESSAGES} entries`);
|
|
33637
|
+
}
|
|
33638
|
+
return value.map((entry, index) => {
|
|
33639
|
+
if (!isPlainRecord(entry)) throw new Error(`messages[${index}] must be an object`);
|
|
33640
|
+
return entry;
|
|
33641
|
+
});
|
|
33642
|
+
}
|
|
33643
|
+
function decodeMessageValue(value, encoding, label) {
|
|
33644
|
+
if (typeof value !== "string") throw new Error(`${label} must be a string`);
|
|
33645
|
+
if (encoding === "base64" && (value.length % 4 !== 0 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value))) {
|
|
33646
|
+
throw new Error(`${label} must be canonical base64`);
|
|
33647
|
+
}
|
|
33648
|
+
const decoded = Buffer.from(value, encoding);
|
|
33649
|
+
if (decoded.byteLength > MAX_MESSAGE_BYTES2) throw new Error(`${label} exceeds the ${MAX_MESSAGE_BYTES2}-byte limit`);
|
|
33650
|
+
return decoded;
|
|
33651
|
+
}
|
|
33652
|
+
function encodeMessageValue(value, encoding) {
|
|
33653
|
+
return value ? value.toString(encoding) : null;
|
|
33654
|
+
}
|
|
33655
|
+
function encodeHeader(value, encoding) {
|
|
33656
|
+
if (value === void 0) return void 0;
|
|
33657
|
+
if (Array.isArray(value)) return value.map((entry) => Buffer.isBuffer(entry) ? entry.toString(encoding) : entry);
|
|
33658
|
+
return Buffer.isBuffer(value) ? value.toString(encoding) : value;
|
|
33659
|
+
}
|
|
33660
|
+
function readHeaders(value) {
|
|
33661
|
+
if (value === void 0 || value === null) return void 0;
|
|
33662
|
+
if (!isPlainRecord(value) || Object.keys(value).length > MAX_HEADERS) throw new Error(`headers must have at most ${MAX_HEADERS} string fields`);
|
|
33663
|
+
const headers = Object.fromEntries(Object.entries(value).map(([key, entry]) => {
|
|
33664
|
+
if (typeof entry !== "string") throw new Error(`header ${key} must be a string`);
|
|
33665
|
+
return [key, entry];
|
|
33666
|
+
}));
|
|
33667
|
+
if (headerBytes(headers) > MAX_MESSAGE_BYTES2) throw new Error(`headers exceed the ${MAX_MESSAGE_BYTES2}-byte limit`);
|
|
33668
|
+
return headers;
|
|
33669
|
+
}
|
|
33670
|
+
function headerBytes(headers) {
|
|
33671
|
+
if (!headers) return 0;
|
|
33672
|
+
return Object.entries(headers).reduce((total, [key, value]) => {
|
|
33673
|
+
const values = Array.isArray(value) ? value : [value];
|
|
33674
|
+
return total + Buffer.byteLength(key, "utf8") + values.reduce((sum, entry) => {
|
|
33675
|
+
if (Buffer.isBuffer(entry)) return sum + entry.byteLength;
|
|
33676
|
+
if (typeof entry === "string") return sum + Buffer.byteLength(entry, "utf8");
|
|
33677
|
+
return sum;
|
|
33678
|
+
}, 0);
|
|
33679
|
+
}, 0);
|
|
33680
|
+
}
|
|
33681
|
+
function emptySchema() {
|
|
33682
|
+
return { type: "object", properties: {}, additionalProperties: false };
|
|
33683
|
+
}
|
|
33684
|
+
function topicSchema() {
|
|
33685
|
+
return {
|
|
33686
|
+
type: "object",
|
|
33687
|
+
properties: { topic: topicNameSchema() },
|
|
33688
|
+
required: ["topic"],
|
|
33689
|
+
additionalProperties: false
|
|
33690
|
+
};
|
|
33691
|
+
}
|
|
33692
|
+
function topicArraySchema() {
|
|
33693
|
+
return { type: "array", minItems: 1, maxItems: MAX_TOPICS, items: topicNameSchema() };
|
|
33694
|
+
}
|
|
33695
|
+
function topicNameSchema() {
|
|
33696
|
+
return { type: "string", minLength: 1, maxLength: 249, pattern: "^[A-Za-z0-9._-]+$" };
|
|
33697
|
+
}
|
|
33698
|
+
function readCapability5(name, description, parameters) {
|
|
33699
|
+
return { name, class: "read", description, parameters };
|
|
33700
|
+
}
|
|
33701
|
+
function mutationCapability5(name, description, parameters) {
|
|
33702
|
+
return { name, class: "mutation", description, parameters, cas: "none", externalEffect: true };
|
|
33703
|
+
}
|
|
33704
|
+
|
|
32759
33705
|
// src/connectors/adapters/lead-connector.ts
|
|
32760
33706
|
var leadConnectorConnector = declarativeRestConnector({
|
|
32761
33707
|
kind: "lead-connector",
|
|
@@ -42934,6 +43880,130 @@ var pipedriveConnector = declarativeRestConnector({
|
|
|
42934
43880
|
]
|
|
42935
43881
|
});
|
|
42936
43882
|
|
|
43883
|
+
// src/connectors/adapters/parquet-files.ts
|
|
43884
|
+
import { parquetReadObjects } from "hyparquet";
|
|
43885
|
+
import { parquetWriteBuffer } from "hyparquet-writer";
|
|
43886
|
+
var MAX_COLUMNS3 = 256;
|
|
43887
|
+
var parquetFilesConnector = {
|
|
43888
|
+
manifest: {
|
|
43889
|
+
kind: "parquet-files",
|
|
43890
|
+
displayName: "Parquet Files",
|
|
43891
|
+
description: "Read bounded Apache Parquet files and create Parquet files from structured records.",
|
|
43892
|
+
auth: { kind: "none" },
|
|
43893
|
+
defaultConsistencyModel: "advisory",
|
|
43894
|
+
category: "database",
|
|
43895
|
+
capabilities: [
|
|
43896
|
+
{
|
|
43897
|
+
name: "parquet.read",
|
|
43898
|
+
class: "read",
|
|
43899
|
+
description: "Read selected columns and a bounded row range from a base64-encoded Parquet file.",
|
|
43900
|
+
parameters: {
|
|
43901
|
+
type: "object",
|
|
43902
|
+
properties: {
|
|
43903
|
+
fileBase64: { type: "string", description: "Parquet file content encoded as canonical base64." },
|
|
43904
|
+
columns: { type: "array", maxItems: MAX_COLUMNS3, items: { type: "string", minLength: 1 } },
|
|
43905
|
+
rowStart: { type: "integer", minimum: 0, default: 0 },
|
|
43906
|
+
rowLimit: { type: "integer", minimum: 1, maximum: MAX_RECORDS, default: MAX_RECORDS }
|
|
43907
|
+
},
|
|
43908
|
+
required: ["fileBase64"],
|
|
43909
|
+
additionalProperties: false
|
|
43910
|
+
}
|
|
43911
|
+
},
|
|
43912
|
+
{
|
|
43913
|
+
name: "parquet.create",
|
|
43914
|
+
class: "mutation",
|
|
43915
|
+
description: "Create a base64-encoded Parquet file from object records with inferred column types.",
|
|
43916
|
+
parameters: {
|
|
43917
|
+
type: "object",
|
|
43918
|
+
properties: {
|
|
43919
|
+
records: { type: "array", minItems: 1, maxItems: MAX_RECORDS, items: { type: "object" } }
|
|
43920
|
+
},
|
|
43921
|
+
required: ["records"],
|
|
43922
|
+
additionalProperties: false
|
|
43923
|
+
},
|
|
43924
|
+
cas: "none",
|
|
43925
|
+
externalEffect: true
|
|
43926
|
+
}
|
|
43927
|
+
]
|
|
43928
|
+
},
|
|
43929
|
+
async executeRead({ capabilityName, args }) {
|
|
43930
|
+
if (capabilityName !== "parquet.read") throw new Error(`Unknown Parquet capability: ${capabilityName}`);
|
|
43931
|
+
const file = readBase64File(args.fileBase64);
|
|
43932
|
+
const columns = readColumns2(args.columns);
|
|
43933
|
+
const rowStart = readBoundedInteger(args.rowStart, 0, 0, Number.MAX_SAFE_INTEGER, "rowStart");
|
|
43934
|
+
const rowLimit = readBoundedInteger(args.rowLimit, MAX_RECORDS, 1, MAX_RECORDS, "rowLimit");
|
|
43935
|
+
const records = await parquetReadObjects({
|
|
43936
|
+
file: asyncBuffer(file),
|
|
43937
|
+
columns,
|
|
43938
|
+
rowStart,
|
|
43939
|
+
rowEnd: rowStart + rowLimit
|
|
43940
|
+
});
|
|
43941
|
+
return {
|
|
43942
|
+
data: { records: jsonSafe(records), recordCount: records.length, rowStart },
|
|
43943
|
+
fetchedAt: Date.now()
|
|
43944
|
+
};
|
|
43945
|
+
},
|
|
43946
|
+
async executeMutation({ capabilityName, args }) {
|
|
43947
|
+
if (capabilityName !== "parquet.create") throw new Error(`Unknown Parquet capability: ${capabilityName}`);
|
|
43948
|
+
const records = readRecords(args.records);
|
|
43949
|
+
const columnNames = [...new Set(records.flatMap((record) => Object.keys(record)))];
|
|
43950
|
+
if (columnNames.length === 0 || columnNames.length > MAX_COLUMNS3) {
|
|
43951
|
+
throw new Error(`records must contain 1 through ${MAX_COLUMNS3} columns`);
|
|
43952
|
+
}
|
|
43953
|
+
if (columnNames.some((name) => name.length === 0)) throw new Error("column names must not be empty");
|
|
43954
|
+
const columnData = columnNames.map((name) => {
|
|
43955
|
+
const data4 = records.map((record) => parquetValue(record[name]));
|
|
43956
|
+
return { name, data: data4, type: inferParquetType(data4) };
|
|
43957
|
+
});
|
|
43958
|
+
const file = parquetWriteBuffer({ columnData });
|
|
43959
|
+
return {
|
|
43960
|
+
status: "committed",
|
|
43961
|
+
data: { ...encodeFile(new Uint8Array(file)), columns: columnNames, recordCount: records.length },
|
|
43962
|
+
committedAt: Date.now(),
|
|
43963
|
+
idempotentReplay: false
|
|
43964
|
+
};
|
|
43965
|
+
},
|
|
43966
|
+
async test() {
|
|
43967
|
+
return { ok: true };
|
|
43968
|
+
}
|
|
43969
|
+
};
|
|
43970
|
+
function asyncBuffer(file) {
|
|
43971
|
+
return {
|
|
43972
|
+
byteLength: file.byteLength,
|
|
43973
|
+
slice(start, end) {
|
|
43974
|
+
const boundedEnd = end === void 0 ? file.byteLength : Math.min(end, file.byteLength);
|
|
43975
|
+
return Uint8Array.from(file.subarray(start, boundedEnd)).buffer;
|
|
43976
|
+
}
|
|
43977
|
+
};
|
|
43978
|
+
}
|
|
43979
|
+
function readColumns2(value) {
|
|
43980
|
+
if (value === void 0 || value === null) return void 0;
|
|
43981
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > MAX_COLUMNS3) {
|
|
43982
|
+
throw new Error(`columns must contain 1 through ${MAX_COLUMNS3} names`);
|
|
43983
|
+
}
|
|
43984
|
+
const columns = value.map((entry, index) => {
|
|
43985
|
+
if (typeof entry !== "string" || entry.length === 0) throw new Error(`columns[${index}] must be a non-empty string`);
|
|
43986
|
+
return entry;
|
|
43987
|
+
});
|
|
43988
|
+
if (new Set(columns).size !== columns.length) throw new Error("columns must not contain duplicates");
|
|
43989
|
+
return columns;
|
|
43990
|
+
}
|
|
43991
|
+
function parquetValue(value) {
|
|
43992
|
+
if (value === void 0) return null;
|
|
43993
|
+
if (typeof value === "number" && !Number.isFinite(value)) throw new Error("Parquet numeric values must be finite");
|
|
43994
|
+
return value;
|
|
43995
|
+
}
|
|
43996
|
+
function inferParquetType(values) {
|
|
43997
|
+
const present = values.filter((value) => value !== null && value !== void 0);
|
|
43998
|
+
if (present.length === 0) return "JSON";
|
|
43999
|
+
if (present.every((value) => typeof value === "boolean")) return "BOOLEAN";
|
|
44000
|
+
if (present.every((value) => typeof value === "string")) return "STRING";
|
|
44001
|
+
if (present.every((value) => typeof value === "number")) return "DOUBLE";
|
|
44002
|
+
if (present.every((value) => typeof value === "bigint")) return "INT64";
|
|
44003
|
+
if (present.every((value) => value instanceof Date)) return "TIMESTAMP";
|
|
44004
|
+
return "JSON";
|
|
44005
|
+
}
|
|
44006
|
+
|
|
42937
44007
|
// src/connectors/adapters/quickbooks.ts
|
|
42938
44008
|
var quickbooksConnector = declarativeRestConnector({
|
|
42939
44009
|
kind: "quickbooks",
|
|
@@ -44194,6 +45264,355 @@ var salesforceConnector = declarativeRestConnector({
|
|
|
44194
45264
|
]
|
|
44195
45265
|
});
|
|
44196
45266
|
|
|
45267
|
+
// src/connectors/adapters/sftp.ts
|
|
45268
|
+
import { timingSafeEqual as timingSafeEqual4 } from "crypto";
|
|
45269
|
+
import { posix } from "path";
|
|
45270
|
+
import SftpClient from "ssh2-sftp-client";
|
|
45271
|
+
var MAX_LIST_ENTRIES = 1e3;
|
|
45272
|
+
var MAX_PATH_LENGTH = 1024;
|
|
45273
|
+
function createSftpConnector(options = {}) {
|
|
45274
|
+
const createClient = options.createClient ?? (() => new SftpClient());
|
|
45275
|
+
const lookupHost = options.lookupHost ?? resolvePublicHostAddresses;
|
|
45276
|
+
return {
|
|
45277
|
+
manifest: {
|
|
45278
|
+
kind: "sftp",
|
|
45279
|
+
displayName: "SFTP",
|
|
45280
|
+
description: "List, inspect, download, upload, move, and delete files over host-key-verified SFTP.",
|
|
45281
|
+
auth: {
|
|
45282
|
+
kind: "api-key",
|
|
45283
|
+
hint: "JSON with host, username, SHA-256 hostFingerprint, and password or privateKey; optional port, passphrase, and rootPath."
|
|
45284
|
+
},
|
|
45285
|
+
defaultConsistencyModel: "advisory",
|
|
45286
|
+
category: "storage",
|
|
45287
|
+
rateLimit: { requests: 120, windowMs: 6e4, scope: "data-source" },
|
|
45288
|
+
capabilities: [
|
|
45289
|
+
{
|
|
45290
|
+
name: "sftp.list",
|
|
45291
|
+
class: "read",
|
|
45292
|
+
description: "List a directory within the connection root.",
|
|
45293
|
+
parameters: {
|
|
45294
|
+
type: "object",
|
|
45295
|
+
properties: {
|
|
45296
|
+
path: { type: "string", default: "/" },
|
|
45297
|
+
limit: { type: "integer", minimum: 1, maximum: MAX_LIST_ENTRIES, default: MAX_LIST_ENTRIES }
|
|
45298
|
+
},
|
|
45299
|
+
additionalProperties: false
|
|
45300
|
+
}
|
|
45301
|
+
},
|
|
45302
|
+
{
|
|
45303
|
+
name: "sftp.stat",
|
|
45304
|
+
class: "read",
|
|
45305
|
+
description: "Read file or directory metadata within the connection root.",
|
|
45306
|
+
parameters: pathSchema()
|
|
45307
|
+
},
|
|
45308
|
+
{
|
|
45309
|
+
name: "sftp.download",
|
|
45310
|
+
class: "read",
|
|
45311
|
+
description: `Download a file no larger than ${MAX_FILE_BYTES} bytes as base64.`,
|
|
45312
|
+
parameters: pathSchema()
|
|
45313
|
+
},
|
|
45314
|
+
{
|
|
45315
|
+
name: "sftp.upload",
|
|
45316
|
+
class: "mutation",
|
|
45317
|
+
description: `Upload a base64 file no larger than ${MAX_FILE_BYTES} bytes within the connection root.`,
|
|
45318
|
+
parameters: {
|
|
45319
|
+
type: "object",
|
|
45320
|
+
properties: {
|
|
45321
|
+
path: { type: "string", minLength: 1 },
|
|
45322
|
+
fileBase64: { type: "string" },
|
|
45323
|
+
overwrite: { type: "boolean", default: false }
|
|
45324
|
+
},
|
|
45325
|
+
required: ["path", "fileBase64"],
|
|
45326
|
+
additionalProperties: false
|
|
45327
|
+
},
|
|
45328
|
+
cas: "none",
|
|
45329
|
+
externalEffect: true
|
|
45330
|
+
},
|
|
45331
|
+
{
|
|
45332
|
+
name: "sftp.mkdir",
|
|
45333
|
+
class: "mutation",
|
|
45334
|
+
description: "Create a directory within the connection root.",
|
|
45335
|
+
parameters: {
|
|
45336
|
+
type: "object",
|
|
45337
|
+
properties: {
|
|
45338
|
+
path: { type: "string", minLength: 1 },
|
|
45339
|
+
recursive: { type: "boolean", default: true }
|
|
45340
|
+
},
|
|
45341
|
+
required: ["path"],
|
|
45342
|
+
additionalProperties: false
|
|
45343
|
+
},
|
|
45344
|
+
cas: "none",
|
|
45345
|
+
externalEffect: true
|
|
45346
|
+
},
|
|
45347
|
+
{
|
|
45348
|
+
name: "sftp.rename",
|
|
45349
|
+
class: "mutation",
|
|
45350
|
+
description: "Move or rename a file without overwriting the destination.",
|
|
45351
|
+
parameters: {
|
|
45352
|
+
type: "object",
|
|
45353
|
+
properties: {
|
|
45354
|
+
sourcePath: { type: "string", minLength: 1 },
|
|
45355
|
+
destinationPath: { type: "string", minLength: 1 }
|
|
45356
|
+
},
|
|
45357
|
+
required: ["sourcePath", "destinationPath"],
|
|
45358
|
+
additionalProperties: false
|
|
45359
|
+
},
|
|
45360
|
+
cas: "none",
|
|
45361
|
+
externalEffect: true
|
|
45362
|
+
},
|
|
45363
|
+
{
|
|
45364
|
+
name: "sftp.delete",
|
|
45365
|
+
class: "mutation",
|
|
45366
|
+
description: "Delete a file within the connection root. Repeated deletion is safe.",
|
|
45367
|
+
parameters: pathSchema(),
|
|
45368
|
+
cas: "none",
|
|
45369
|
+
externalEffect: true
|
|
45370
|
+
}
|
|
45371
|
+
]
|
|
45372
|
+
},
|
|
45373
|
+
async executeRead({ source, capabilityName, args }) {
|
|
45374
|
+
const credentials = readCredentials2(source);
|
|
45375
|
+
if (capabilityName === "sftp.list") {
|
|
45376
|
+
const path = resolveRemotePath(credentials.rootPath, args.path ?? "/");
|
|
45377
|
+
const limit = readBoundedInteger(args.limit, MAX_LIST_ENTRIES, 1, MAX_LIST_ENTRIES, "limit");
|
|
45378
|
+
return withSftp(credentials, createClient, lookupHost, async (client) => {
|
|
45379
|
+
const safePath = await canonicalizeExistingPath(client, credentials.rootPath, path);
|
|
45380
|
+
const allEntries = await client.list(safePath);
|
|
45381
|
+
const entries = allEntries.slice(0, limit);
|
|
45382
|
+
return { data: { path: safePath, entries, truncated: allEntries.length > limit }, fetchedAt: Date.now() };
|
|
45383
|
+
});
|
|
45384
|
+
}
|
|
45385
|
+
if (capabilityName === "sftp.stat") {
|
|
45386
|
+
const path = resolveRemotePath(credentials.rootPath, args.path);
|
|
45387
|
+
return withSftp(credentials, createClient, lookupHost, async (client) => {
|
|
45388
|
+
const safePath = await canonicalizeExistingPath(client, credentials.rootPath, path);
|
|
45389
|
+
return { data: { path: safePath, stat: await client.stat(safePath) }, fetchedAt: Date.now() };
|
|
45390
|
+
});
|
|
45391
|
+
}
|
|
45392
|
+
if (capabilityName === "sftp.download") {
|
|
45393
|
+
const path = resolveRemotePath(credentials.rootPath, args.path);
|
|
45394
|
+
return withSftp(credentials, createClient, lookupHost, async (client) => {
|
|
45395
|
+
const safePath = await canonicalizeExistingPath(client, credentials.rootPath, path);
|
|
45396
|
+
const stat = await client.stat(safePath);
|
|
45397
|
+
if (!stat.isFile) throw new Error("sftp.download path is not a file");
|
|
45398
|
+
if (stat.size > MAX_FILE_BYTES) throw new Error(`sftp.download file exceeds the ${MAX_FILE_BYTES}-byte limit`);
|
|
45399
|
+
const file = await client.get(safePath);
|
|
45400
|
+
if (!Buffer.isBuffer(file)) throw new Error("SFTP client returned a non-buffer download");
|
|
45401
|
+
return { data: { path: safePath, ...encodeFile(file), modifyTime: stat.modifyTime }, fetchedAt: Date.now() };
|
|
45402
|
+
});
|
|
45403
|
+
}
|
|
45404
|
+
throw new Error(`Unknown SFTP read capability: ${capabilityName}`);
|
|
45405
|
+
},
|
|
45406
|
+
async executeMutation({ source, capabilityName, args }) {
|
|
45407
|
+
const credentials = readCredentials2(source);
|
|
45408
|
+
const prepared = prepareMutation(capabilityName, args, credentials.rootPath);
|
|
45409
|
+
return withSftp(credentials, createClient, lookupHost, async (client) => {
|
|
45410
|
+
let data4;
|
|
45411
|
+
if (prepared.kind === "upload") {
|
|
45412
|
+
const path = await canonicalizeDestinationPath(client, credentials.rootPath, prepared.path);
|
|
45413
|
+
if (!prepared.overwrite && await client.exists(path)) throw new Error("sftp.upload destination already exists");
|
|
45414
|
+
await client.put(prepared.file, path);
|
|
45415
|
+
data4 = { path, byteLength: prepared.file.byteLength };
|
|
45416
|
+
} else if (prepared.kind === "mkdir") {
|
|
45417
|
+
const path = await canonicalizeDestinationPath(client, credentials.rootPath, prepared.path);
|
|
45418
|
+
await client.mkdir(path, prepared.recursive);
|
|
45419
|
+
data4 = { path };
|
|
45420
|
+
} else if (prepared.kind === "rename") {
|
|
45421
|
+
const sourcePath = await canonicalizeExistingPath(client, credentials.rootPath, prepared.sourcePath);
|
|
45422
|
+
const destinationPath = await canonicalizeDestinationPath(client, credentials.rootPath, prepared.destinationPath);
|
|
45423
|
+
if (await client.exists(destinationPath)) throw new Error("sftp.rename destination already exists");
|
|
45424
|
+
await client.rename(sourcePath, destinationPath);
|
|
45425
|
+
data4 = { sourcePath, destinationPath };
|
|
45426
|
+
} else {
|
|
45427
|
+
const path = await canonicalizeDestinationPath(client, credentials.rootPath, prepared.path);
|
|
45428
|
+
await client.delete(path, true);
|
|
45429
|
+
data4 = { path };
|
|
45430
|
+
}
|
|
45431
|
+
return {
|
|
45432
|
+
status: "committed",
|
|
45433
|
+
data: data4,
|
|
45434
|
+
committedAt: Date.now(),
|
|
45435
|
+
idempotentReplay: false
|
|
45436
|
+
};
|
|
45437
|
+
});
|
|
45438
|
+
},
|
|
45439
|
+
async test(source) {
|
|
45440
|
+
try {
|
|
45441
|
+
const credentials = readCredentials2(source);
|
|
45442
|
+
await withSftp(credentials, createClient, lookupHost, (client) => client.cwd());
|
|
45443
|
+
return { ok: true };
|
|
45444
|
+
} catch (error) {
|
|
45445
|
+
return { ok: false, reason: error instanceof Error ? error.message : "SFTP connection test failed" };
|
|
45446
|
+
}
|
|
45447
|
+
}
|
|
45448
|
+
};
|
|
45449
|
+
}
|
|
45450
|
+
function prepareMutation(capabilityName, args, rootPath) {
|
|
45451
|
+
if (capabilityName === "sftp.upload") {
|
|
45452
|
+
return {
|
|
45453
|
+
kind: "upload",
|
|
45454
|
+
path: mutablePath(rootPath, args.path),
|
|
45455
|
+
file: readBase64File(args.fileBase64),
|
|
45456
|
+
overwrite: readBoolean(args.overwrite, false, "overwrite")
|
|
45457
|
+
};
|
|
45458
|
+
}
|
|
45459
|
+
if (capabilityName === "sftp.mkdir") {
|
|
45460
|
+
return {
|
|
45461
|
+
kind: "mkdir",
|
|
45462
|
+
path: mutablePath(rootPath, args.path),
|
|
45463
|
+
recursive: readBoolean(args.recursive, true, "recursive")
|
|
45464
|
+
};
|
|
45465
|
+
}
|
|
45466
|
+
if (capabilityName === "sftp.rename") {
|
|
45467
|
+
return {
|
|
45468
|
+
kind: "rename",
|
|
45469
|
+
sourcePath: mutablePath(rootPath, args.sourcePath),
|
|
45470
|
+
destinationPath: mutablePath(rootPath, args.destinationPath)
|
|
45471
|
+
};
|
|
45472
|
+
}
|
|
45473
|
+
if (capabilityName === "sftp.delete") {
|
|
45474
|
+
return { kind: "delete", path: mutablePath(rootPath, args.path) };
|
|
45475
|
+
}
|
|
45476
|
+
throw new Error(`Unknown SFTP mutation capability: ${capabilityName}`);
|
|
45477
|
+
}
|
|
45478
|
+
var sftpConnector = createSftpConnector();
|
|
45479
|
+
function pathSchema() {
|
|
45480
|
+
return {
|
|
45481
|
+
type: "object",
|
|
45482
|
+
properties: { path: { type: "string", minLength: 1 } },
|
|
45483
|
+
required: ["path"],
|
|
45484
|
+
additionalProperties: false
|
|
45485
|
+
};
|
|
45486
|
+
}
|
|
45487
|
+
async function withSftp(credentials, createClient, lookupHost, run) {
|
|
45488
|
+
const addresses = await lookupHost(credentials.host);
|
|
45489
|
+
if (addresses.length === 0) throw new Error("SFTP host did not resolve");
|
|
45490
|
+
const client = createClient();
|
|
45491
|
+
try {
|
|
45492
|
+
await client.connect({
|
|
45493
|
+
host: addresses[0],
|
|
45494
|
+
port: credentials.port,
|
|
45495
|
+
username: credentials.username,
|
|
45496
|
+
password: credentials.password,
|
|
45497
|
+
privateKey: credentials.privateKey,
|
|
45498
|
+
passphrase: credentials.passphrase,
|
|
45499
|
+
readyTimeout: 1e4,
|
|
45500
|
+
retries: 1,
|
|
45501
|
+
hostHash: "sha256",
|
|
45502
|
+
hostVerifier: (hashedKey) => fingerprintMatches(credentials.hostFingerprint, hashedKey)
|
|
45503
|
+
});
|
|
45504
|
+
return await run(client);
|
|
45505
|
+
} finally {
|
|
45506
|
+
await client.end().catch(() => false);
|
|
45507
|
+
}
|
|
45508
|
+
}
|
|
45509
|
+
function readCredentials2(source) {
|
|
45510
|
+
let raw;
|
|
45511
|
+
if (source.credentials.kind === "custom") raw = source.credentials.values;
|
|
45512
|
+
else if (source.credentials.kind === "api-key") {
|
|
45513
|
+
try {
|
|
45514
|
+
raw = JSON.parse(source.credentials.apiKey);
|
|
45515
|
+
} catch {
|
|
45516
|
+
throw new Error("SFTP credential must be valid JSON");
|
|
45517
|
+
}
|
|
45518
|
+
} else {
|
|
45519
|
+
throw new Error("SFTP requires a structured credential bundle");
|
|
45520
|
+
}
|
|
45521
|
+
if (!isPlainRecord(raw)) throw new Error("SFTP credential must be a JSON object");
|
|
45522
|
+
const host = requiredCredentialString2(raw.host, "host");
|
|
45523
|
+
validateHostname(host);
|
|
45524
|
+
const username = requiredCredentialString2(raw.username, "username");
|
|
45525
|
+
const password = readOptionalString(raw.password, "password");
|
|
45526
|
+
const privateKey = readOptionalString(raw.privateKey, "privateKey");
|
|
45527
|
+
if (!password && !privateKey) throw new Error("SFTP credential requires password or privateKey");
|
|
45528
|
+
const rootPath = normalizeRootPath(readOptionalString(raw.rootPath, "rootPath") ?? "/");
|
|
45529
|
+
return {
|
|
45530
|
+
host,
|
|
45531
|
+
port: readBoundedInteger(raw.port, 22, 1, 65535, "port"),
|
|
45532
|
+
username,
|
|
45533
|
+
password,
|
|
45534
|
+
privateKey,
|
|
45535
|
+
passphrase: readOptionalString(raw.passphrase, "passphrase"),
|
|
45536
|
+
hostFingerprint: parseFingerprint(requiredCredentialString2(raw.hostFingerprint, "hostFingerprint")),
|
|
45537
|
+
rootPath
|
|
45538
|
+
};
|
|
45539
|
+
}
|
|
45540
|
+
function requiredCredentialString2(value, label) {
|
|
45541
|
+
const parsed = readOptionalString(value, label);
|
|
45542
|
+
if (!parsed) throw new Error(`SFTP credential ${label} is required`);
|
|
45543
|
+
return parsed;
|
|
45544
|
+
}
|
|
45545
|
+
function validateHostname(host) {
|
|
45546
|
+
if (host.length > 253 || host.includes("://") || /[\s/@]/.test(host) || host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local")) {
|
|
45547
|
+
throw new Error("SFTP host must be a public hostname or IP address without a scheme");
|
|
45548
|
+
}
|
|
45549
|
+
}
|
|
45550
|
+
function parseFingerprint(value) {
|
|
45551
|
+
if (/^[a-fA-F0-9]{64}$/.test(value)) return Buffer.from(value, "hex");
|
|
45552
|
+
const match = /^SHA256:([A-Za-z0-9+/]{43}=?)$/.exec(value);
|
|
45553
|
+
if (!match) throw new Error("hostFingerprint must be 64 hex characters or an SHA256: base64 fingerprint");
|
|
45554
|
+
const parsed = Buffer.from(match[1], "base64");
|
|
45555
|
+
if (parsed.byteLength !== 32) throw new Error("hostFingerprint must contain a 32-byte SHA-256 digest");
|
|
45556
|
+
return parsed;
|
|
45557
|
+
}
|
|
45558
|
+
function fingerprintMatches(expected, actual) {
|
|
45559
|
+
const parsed = Buffer.isBuffer(actual) ? actual : /^[a-fA-F0-9]{64}$/.test(actual) ? Buffer.from(actual, "hex") : Buffer.from(actual.replace(/^SHA256:/, ""), "base64");
|
|
45560
|
+
return parsed.byteLength === expected.byteLength && timingSafeEqual4(parsed, expected);
|
|
45561
|
+
}
|
|
45562
|
+
function normalizeRootPath(value) {
|
|
45563
|
+
if (!value.startsWith("/") || value.includes("\0") || value.length > MAX_PATH_LENGTH) {
|
|
45564
|
+
throw new Error("rootPath must be an absolute SFTP path");
|
|
45565
|
+
}
|
|
45566
|
+
return posix.normalize(value);
|
|
45567
|
+
}
|
|
45568
|
+
function resolveRemotePath(rootPath, value) {
|
|
45569
|
+
const requested = readOptionalString(value, "path");
|
|
45570
|
+
if (!requested || requested.includes("\0") || requested.length > MAX_PATH_LENGTH) {
|
|
45571
|
+
throw new Error("path must be a non-empty SFTP path");
|
|
45572
|
+
}
|
|
45573
|
+
const resolved = posix.resolve(rootPath, requested.replace(/^\/+/, ""));
|
|
45574
|
+
if (rootPath !== "/" && resolved !== rootPath && !resolved.startsWith(`${rootPath}/`)) {
|
|
45575
|
+
throw new Error("path escapes the configured SFTP root");
|
|
45576
|
+
}
|
|
45577
|
+
return resolved;
|
|
45578
|
+
}
|
|
45579
|
+
function mutablePath(rootPath, value) {
|
|
45580
|
+
const path = resolveRemotePath(rootPath, value);
|
|
45581
|
+
if (path === rootPath) throw new Error("mutation path cannot target the configured SFTP root");
|
|
45582
|
+
return path;
|
|
45583
|
+
}
|
|
45584
|
+
async function canonicalizeExistingPath(client, configuredRoot, path) {
|
|
45585
|
+
const root = posix.normalize(await client.realPath(configuredRoot));
|
|
45586
|
+
const target = posix.normalize(await client.realPath(path));
|
|
45587
|
+
assertWithinCanonicalRoot(root, target);
|
|
45588
|
+
return target;
|
|
45589
|
+
}
|
|
45590
|
+
async function canonicalizeDestinationPath(client, configuredRoot, path) {
|
|
45591
|
+
const root = posix.normalize(await client.realPath(configuredRoot));
|
|
45592
|
+
if (await client.exists(path)) {
|
|
45593
|
+
const existing = posix.normalize(await client.realPath(path));
|
|
45594
|
+
assertWithinCanonicalRoot(root, existing);
|
|
45595
|
+
return existing;
|
|
45596
|
+
}
|
|
45597
|
+
let ancestor = posix.dirname(path);
|
|
45598
|
+
const missingSegments = [posix.basename(path)];
|
|
45599
|
+
while (!await client.exists(ancestor)) {
|
|
45600
|
+
if (ancestor === configuredRoot || ancestor === "/") break;
|
|
45601
|
+
missingSegments.unshift(posix.basename(ancestor));
|
|
45602
|
+
ancestor = posix.dirname(ancestor);
|
|
45603
|
+
}
|
|
45604
|
+
const canonicalAncestor = posix.normalize(await client.realPath(ancestor));
|
|
45605
|
+
assertWithinCanonicalRoot(root, canonicalAncestor);
|
|
45606
|
+
const destination = posix.join(canonicalAncestor, ...missingSegments);
|
|
45607
|
+
assertWithinCanonicalRoot(root, destination);
|
|
45608
|
+
return destination;
|
|
45609
|
+
}
|
|
45610
|
+
function assertWithinCanonicalRoot(root, target) {
|
|
45611
|
+
if (root !== "/" && target !== root && !target.startsWith(`${root}/`)) {
|
|
45612
|
+
throw new Error("SFTP path resolves outside the configured root");
|
|
45613
|
+
}
|
|
45614
|
+
}
|
|
45615
|
+
|
|
44197
45616
|
// src/connectors/adapters/saleor.ts
|
|
44198
45617
|
var saleorConnector = declarativeRestConnector({
|
|
44199
45618
|
kind: "saleor",
|
|
@@ -53790,17 +55209,17 @@ var zohoBookingsConnector = {
|
|
|
53790
55209
|
category: "calendar",
|
|
53791
55210
|
defaultConsistencyModel: "authoritative",
|
|
53792
55211
|
capabilities: [
|
|
53793
|
-
|
|
53794
|
-
|
|
55212
|
+
readCapability6("workspaces.list", "List Zoho Bookings workspaces.", {}),
|
|
55213
|
+
readCapability6("services.list", "List services, optionally within one workspace.", {
|
|
53795
55214
|
workspace_id: { type: "string" }
|
|
53796
55215
|
}),
|
|
53797
|
-
|
|
55216
|
+
readCapability6("resources.list", "List bookable resources, optionally for one service.", {
|
|
53798
55217
|
service_id: { type: "string" }
|
|
53799
55218
|
}),
|
|
53800
|
-
|
|
55219
|
+
readCapability6("staff.list", "List staff, optionally for one service.", {
|
|
53801
55220
|
service_id: { type: "string" }
|
|
53802
55221
|
}),
|
|
53803
|
-
|
|
55222
|
+
readCapability6("appointments.list", "List appointments from a required start time.", {
|
|
53804
55223
|
from_time: {
|
|
53805
55224
|
type: "string",
|
|
53806
55225
|
description: "Zoho timestamp in dd-MMM-yyyy HH:mm:ss format."
|
|
@@ -53810,17 +55229,17 @@ var zohoBookingsConnector = {
|
|
|
53810
55229
|
staff_id: { type: "string" },
|
|
53811
55230
|
status: { type: "string" }
|
|
53812
55231
|
}, ["from_time"]),
|
|
53813
|
-
|
|
55232
|
+
readCapability6("appointments.get", "Read one appointment by booking id.", {
|
|
53814
55233
|
booking_id: { type: "string" }
|
|
53815
55234
|
}, ["booking_id"]),
|
|
53816
|
-
|
|
55235
|
+
readCapability6("availability.fetch", "Fetch open time slots for a service and date.", {
|
|
53817
55236
|
service_id: { type: "string" },
|
|
53818
55237
|
selected_date: { type: "string", description: "Date in YYYY-MM-DD format." },
|
|
53819
55238
|
staff_id: { type: "string" },
|
|
53820
55239
|
group_id: { type: "string" },
|
|
53821
55240
|
resource_id: { type: "string" }
|
|
53822
55241
|
}, ["service_id", "selected_date"]),
|
|
53823
|
-
|
|
55242
|
+
mutationCapability6("appointments.book", "Book an appointment for a customer.", {
|
|
53824
55243
|
service_id: { type: "string" },
|
|
53825
55244
|
from_time: { type: "string", description: "Zoho timestamp in dd-MMM-yyyy HH:mm:ss format." },
|
|
53826
55245
|
customer_details: {
|
|
@@ -53836,13 +55255,13 @@ var zohoBookingsConnector = {
|
|
|
53836
55255
|
additional_fields: { type: "object" },
|
|
53837
55256
|
payment_info: { type: "object" }
|
|
53838
55257
|
}, ["service_id", "from_time", "customer_details"]),
|
|
53839
|
-
|
|
55258
|
+
mutationCapability6("appointments.reschedule", "Move an appointment to a new time, staff member, or group.", {
|
|
53840
55259
|
booking_id: { type: "string" },
|
|
53841
55260
|
start_time: { type: "string", description: "Zoho timestamp in dd-MMM-yyyy HH:mm:ss format." },
|
|
53842
55261
|
staff_id: { type: "string" },
|
|
53843
55262
|
group_id: { type: "string" }
|
|
53844
55263
|
}, ["booking_id"]),
|
|
53845
|
-
|
|
55264
|
+
mutationCapability6("appointments.cancel", "Cancel an appointment by booking id.", {
|
|
53846
55265
|
booking_id: { type: "string" }
|
|
53847
55266
|
}, ["booking_id"])
|
|
53848
55267
|
]
|
|
@@ -53959,7 +55378,7 @@ var zohoBookingsConnector = {
|
|
|
53959
55378
|
}
|
|
53960
55379
|
}
|
|
53961
55380
|
};
|
|
53962
|
-
function
|
|
55381
|
+
function readCapability6(name, description, properties, required = []) {
|
|
53963
55382
|
return {
|
|
53964
55383
|
name,
|
|
53965
55384
|
class: "read",
|
|
@@ -53968,7 +55387,7 @@ function readCapability5(name, description, properties, required = []) {
|
|
|
53968
55387
|
requiredScopes: ["zohobookings.data.READ"]
|
|
53969
55388
|
};
|
|
53970
55389
|
}
|
|
53971
|
-
function
|
|
55390
|
+
function mutationCapability6(name, description, properties, required) {
|
|
53972
55391
|
return {
|
|
53973
55392
|
name,
|
|
53974
55393
|
class: "mutation",
|
|
@@ -62864,6 +64283,11 @@ var CONNECTOR_ADAPTER_FACTORIES = [
|
|
|
62864
64283
|
defineFactoryAdapter(() => amazonSqsConnector, {}),
|
|
62865
64284
|
defineFactoryAdapter(() => httpConnector, {}),
|
|
62866
64285
|
defineFactoryAdapter(() => rssConnector, {}),
|
|
64286
|
+
defineFactoryAdapter(() => kafkaConnector, {}),
|
|
64287
|
+
defineFactoryAdapter(() => csvFilesConnector, {}),
|
|
64288
|
+
defineFactoryAdapter(() => excelFilesConnector, {}),
|
|
64289
|
+
defineFactoryAdapter(() => parquetFilesConnector, {}),
|
|
64290
|
+
defineFactoryAdapter(() => sftpConnector, {}),
|
|
62867
64291
|
defineFactoryAdapter(() => chessComConnector, {}),
|
|
62868
64292
|
defineFactoryAdapter(() => zapierConnector, {}),
|
|
62869
64293
|
defineFactoryAdapter(() => makeConnector, {}),
|
|
@@ -68840,7 +70264,7 @@ var sanityConnector = declarativeRestConnector({
|
|
|
68840
70264
|
var figmaConnector = declarativeRestConnector({
|
|
68841
70265
|
kind: "figma",
|
|
68842
70266
|
displayName: "Figma",
|
|
68843
|
-
description: "Read Figma files, components, comments, and
|
|
70267
|
+
description: "Read Figma files, components, comments, and project metadata; post comments back into the design source of truth.",
|
|
68844
70268
|
auth: {
|
|
68845
70269
|
kind: "oauth2",
|
|
68846
70270
|
authorizationUrl: "https://www.figma.com/oauth",
|
|
@@ -68853,7 +70277,7 @@ var figmaConnector = declarativeRestConnector({
|
|
|
68853
70277
|
"file_dev_resources:read",
|
|
68854
70278
|
"file_versions:read",
|
|
68855
70279
|
"library_content:read",
|
|
68856
|
-
"
|
|
70280
|
+
"project_metadata:read",
|
|
68857
70281
|
"team_library_content:read",
|
|
68858
70282
|
"webhooks:write"
|
|
68859
70283
|
],
|
|
@@ -69069,35 +70493,16 @@ var figmaConnector = declarativeRestConnector({
|
|
|
69069
70493
|
requiredScopes: ["file_comments:read"]
|
|
69070
70494
|
},
|
|
69071
70495
|
{
|
|
69072
|
-
name: "
|
|
70496
|
+
name: "projects.metadata.get",
|
|
69073
70497
|
class: "read",
|
|
69074
|
-
description: "
|
|
70498
|
+
description: "Get metadata for a Figma project visible to the authenticated user.",
|
|
69075
70499
|
parameters: {
|
|
69076
70500
|
type: "object",
|
|
69077
|
-
properties: {
|
|
69078
|
-
required: ["team_id"]
|
|
69079
|
-
},
|
|
69080
|
-
request: { method: "GET", path: "/v1/teams/{team_id}/projects" },
|
|
69081
|
-
requiredScopes: ["projects:read"]
|
|
69082
|
-
},
|
|
69083
|
-
{
|
|
69084
|
-
name: "projects.files.list",
|
|
69085
|
-
class: "read",
|
|
69086
|
-
description: "List Figma files inside a project.",
|
|
69087
|
-
parameters: {
|
|
69088
|
-
type: "object",
|
|
69089
|
-
properties: {
|
|
69090
|
-
project_id: { type: "string" },
|
|
69091
|
-
branch_data: { type: "boolean" }
|
|
69092
|
-
},
|
|
70501
|
+
properties: { project_id: { type: "string" } },
|
|
69093
70502
|
required: ["project_id"]
|
|
69094
70503
|
},
|
|
69095
|
-
request: {
|
|
69096
|
-
|
|
69097
|
-
path: "/v1/projects/{project_id}/files",
|
|
69098
|
-
query: { branch_data: "{branch_data}" }
|
|
69099
|
-
},
|
|
69100
|
-
requiredScopes: ["projects:read"]
|
|
70504
|
+
request: { method: "GET", path: "/v1/projects/{project_id}/meta" },
|
|
70505
|
+
requiredScopes: ["project_metadata:read"]
|
|
69101
70506
|
},
|
|
69102
70507
|
{
|
|
69103
70508
|
name: "teams.components.list",
|
|
@@ -69264,7 +70669,7 @@ var figmaConnector = declarativeRestConnector({
|
|
|
69264
70669
|
var figjamConnector = declarativeRestConnector({
|
|
69265
70670
|
kind: "figjam",
|
|
69266
70671
|
displayName: "FigJam",
|
|
69267
|
-
description: "Read FigJam boards (stickies, connectors, shapes, sections),
|
|
70672
|
+
description: "Read FigJam boards (stickies, connectors, shapes, sections), project metadata, render board images, and post or delete board comments through the Figma REST API.",
|
|
69268
70673
|
auth: {
|
|
69269
70674
|
kind: "oauth2",
|
|
69270
70675
|
authorizationUrl: "https://www.figma.com/oauth",
|
|
@@ -69275,7 +70680,7 @@ var figjamConnector = declarativeRestConnector({
|
|
|
69275
70680
|
"file_comments:write",
|
|
69276
70681
|
"file_content:read",
|
|
69277
70682
|
"file_versions:read",
|
|
69278
|
-
"
|
|
70683
|
+
"project_metadata:read",
|
|
69279
70684
|
"webhooks:write"
|
|
69280
70685
|
],
|
|
69281
70686
|
clientIdEnv: "FIGMA_OAUTH_CLIENT_ID",
|
|
@@ -69433,33 +70838,16 @@ var figjamConnector = declarativeRestConnector({
|
|
|
69433
70838
|
requiredScopes: ["file_comments:read"]
|
|
69434
70839
|
},
|
|
69435
70840
|
{
|
|
69436
|
-
name: "
|
|
69437
|
-
class: "read",
|
|
69438
|
-
description: "List projects inside a Figma team (FigJam boards live alongside Figma design files inside projects).",
|
|
69439
|
-
parameters: {
|
|
69440
|
-
type: "object",
|
|
69441
|
-
properties: { team_id: { type: "string" } },
|
|
69442
|
-
required: ["team_id"]
|
|
69443
|
-
},
|
|
69444
|
-
request: { method: "GET", path: "/v1/teams/{team_id}/projects" },
|
|
69445
|
-
requiredScopes: ["projects:read"]
|
|
69446
|
-
},
|
|
69447
|
-
{
|
|
69448
|
-
name: "projects.files.list",
|
|
70841
|
+
name: "projects.metadata.get",
|
|
69449
70842
|
class: "read",
|
|
69450
|
-
description:
|
|
70843
|
+
description: "Get metadata for a Figma/FigJam project visible to the authenticated user.",
|
|
69451
70844
|
parameters: {
|
|
69452
70845
|
type: "object",
|
|
69453
|
-
properties: {
|
|
69454
|
-
project_id: { type: "string" }
|
|
69455
|
-
},
|
|
70846
|
+
properties: { project_id: { type: "string" } },
|
|
69456
70847
|
required: ["project_id"]
|
|
69457
70848
|
},
|
|
69458
|
-
request: {
|
|
69459
|
-
|
|
69460
|
-
path: "/v1/projects/{project_id}/files"
|
|
69461
|
-
},
|
|
69462
|
-
requiredScopes: ["projects:read"]
|
|
70849
|
+
request: { method: "GET", path: "/v1/projects/{project_id}/meta" },
|
|
70850
|
+
requiredScopes: ["project_metadata:read"]
|
|
69463
70851
|
},
|
|
69464
70852
|
{
|
|
69465
70853
|
name: "files.comments.create",
|
|
@@ -142735,6 +144123,7 @@ export {
|
|
|
142735
144123
|
buttondownConnector,
|
|
142736
144124
|
cambAiConnector,
|
|
142737
144125
|
cartloomConnector,
|
|
144126
|
+
csvFilesConnector,
|
|
142738
144127
|
chainAwareConnector,
|
|
142739
144128
|
chaindeskConnector,
|
|
142740
144129
|
chatAidConnector,
|
|
@@ -142754,6 +144143,7 @@ export {
|
|
|
142754
144143
|
dripConnector,
|
|
142755
144144
|
echowinConnector,
|
|
142756
144145
|
elevenlabsConnector,
|
|
144146
|
+
excelFilesConnector,
|
|
142757
144147
|
everhourConnector,
|
|
142758
144148
|
featheryConnector,
|
|
142759
144149
|
fellowConnector,
|
|
@@ -142857,6 +144247,9 @@ export {
|
|
|
142857
144247
|
jiraCloudConnector,
|
|
142858
144248
|
jotformConnector,
|
|
142859
144249
|
justcallConnector,
|
|
144250
|
+
createKafkaConnector,
|
|
144251
|
+
kafkaConnector,
|
|
144252
|
+
publicKafkaSocketFactory,
|
|
142860
144253
|
leadConnectorConnector,
|
|
142861
144254
|
lemonSqueezyConnector,
|
|
142862
144255
|
linearConnector,
|
|
@@ -142892,12 +144285,15 @@ export {
|
|
|
142892
144285
|
plaidConnector,
|
|
142893
144286
|
pipedreamConnector,
|
|
142894
144287
|
pipedriveConnector,
|
|
144288
|
+
parquetFilesConnector,
|
|
142895
144289
|
quickbooksConnector,
|
|
142896
144290
|
recallAiConnector,
|
|
142897
144291
|
rampConnector,
|
|
142898
144292
|
ringcentralConnector,
|
|
142899
144293
|
rssConnector,
|
|
142900
144294
|
salesforceConnector,
|
|
144295
|
+
createSftpConnector,
|
|
144296
|
+
sftpConnector,
|
|
142901
144297
|
saleorConnector,
|
|
142902
144298
|
savvycalConnector,
|
|
142903
144299
|
sharepoint,
|
|
@@ -143349,4 +144745,4 @@ export {
|
|
|
143349
144745
|
demandbaseConnector,
|
|
143350
144746
|
adapters_exports
|
|
143351
144747
|
};
|
|
143352
|
-
//# sourceMappingURL=chunk-
|
|
144748
|
+
//# sourceMappingURL=chunk-5LDZLVKP.js.map
|