@tangle-network/agent-integrations 0.53.29 → 0.53.32
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 +5 -5
- package/dist/{chunk-XGVHAXTB.js → chunk-5MHN5LWD.js} +2 -2
- package/dist/{chunk-XVXTWH3O.js → chunk-7GS2RYBA.js} +3 -3
- package/dist/{chunk-ARONCYSG.js → chunk-FOIC32DF.js} +2060 -106
- package/dist/{chunk-ARONCYSG.js.map → chunk-FOIC32DF.js.map} +1 -1
- package/dist/{chunk-LBJJMBVE.js → chunk-M3CRVHGW.js} +4 -2
- package/dist/chunk-M3CRVHGW.js.map +1 -0
- package/dist/{chunk-U4OI4QU3.js → chunk-UUXEF2QZ.js} +105 -5
- package/dist/chunk-UUXEF2QZ.js.map +1 -0
- package/dist/connectors/adapters/index.d.ts +82 -1
- package/dist/connectors/adapters/index.js +21 -1
- package/dist/connectors/index.d.ts +5 -1
- package/dist/connectors/index.js +22 -2
- package/dist/coverage-catalog.js +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +25 -5
- package/dist/mcp.js +5 -5
- package/dist/registry.js +5 -5
- package/dist/runtime.js +5 -5
- package/dist/specs.js +4 -4
- package/docs/integration-execution-audit.md +25 -19
- package/docs/integration-execution-matrix.json +404 -434
- package/package.json +17 -10
- package/dist/chunk-LBJJMBVE.js.map +0 -1
- package/dist/chunk-U4OI4QU3.js.map +0 -1
- /package/dist/{chunk-XGVHAXTB.js.map → chunk-5MHN5LWD.js.map} +0 -0
- /package/dist/{chunk-XVXTWH3O.js.map → chunk-7GS2RYBA.js.map} +0 -0
|
@@ -181,6 +181,10 @@ __export(adapters_exports, {
|
|
|
181
181
|
copperConnector: () => copperConnector,
|
|
182
182
|
copyAiConnector: () => copyAiConnector,
|
|
183
183
|
couchbaseConnector: () => couchbaseConnector,
|
|
184
|
+
createKafkaConnector: () => createKafkaConnector,
|
|
185
|
+
createMySqlConnector: () => createMySqlConnector,
|
|
186
|
+
createRabbitMqConnector: () => createRabbitMqConnector,
|
|
187
|
+
createRedisConnector: () => createRedisConnector,
|
|
184
188
|
createSftpConnector: () => createSftpConnector,
|
|
185
189
|
createTangleIdentityClient: () => createTangleIdentityClient,
|
|
186
190
|
crispConnector: () => crispConnector,
|
|
@@ -217,6 +221,7 @@ __export(adapters_exports, {
|
|
|
217
221
|
dripConnector: () => dripConnector,
|
|
218
222
|
dropboxConnector: () => dropboxConnector,
|
|
219
223
|
dubConnector: () => dubConnector,
|
|
224
|
+
duckdbConnector: () => duckdbConnector,
|
|
220
225
|
dumplingAiConnector: () => dumplingAiConnector,
|
|
221
226
|
dustConnector: () => dustConnector,
|
|
222
227
|
easyPeasyAiConnector: () => easyPeasyAiConnector,
|
|
@@ -352,6 +357,7 @@ __export(adapters_exports, {
|
|
|
352
357
|
jotformConnector: () => jotformConnector,
|
|
353
358
|
justInvoiceConnector: () => justInvoiceConnector,
|
|
354
359
|
justcallConnector: () => justcallConnector,
|
|
360
|
+
kafkaConnector: () => kafkaConnector,
|
|
355
361
|
kallabotAiConnector: () => kallabotAiConnector,
|
|
356
362
|
kapsoConnector: () => kapsoConnector,
|
|
357
363
|
kimaiConnector: () => kimaiConnector,
|
|
@@ -436,6 +442,7 @@ __export(adapters_exports, {
|
|
|
436
442
|
mondayConnector: () => mondayConnector,
|
|
437
443
|
mongodbConnector: () => mongodbConnector,
|
|
438
444
|
mycaseConnector: () => mycaseConnector,
|
|
445
|
+
mysqlConnector: () => mysqlConnector,
|
|
439
446
|
n8nConnector: () => n8nConnector,
|
|
440
447
|
netlifyConnector: () => netlifyConnector,
|
|
441
448
|
netsuiteConnector: () => netsuiteConnector,
|
|
@@ -498,6 +505,7 @@ __export(adapters_exports, {
|
|
|
498
505
|
predictLeadsConnector: () => predictLeadsConnector,
|
|
499
506
|
productboardConnector: () => productboardConnector,
|
|
500
507
|
proxycurlConnector: () => proxycurlConnector,
|
|
508
|
+
publicKafkaSocketFactory: () => publicKafkaSocketFactory,
|
|
501
509
|
pushbulletConnector: () => pushbulletConnector,
|
|
502
510
|
pushoverConnector: () => pushoverConnector,
|
|
503
511
|
qawafelConnector: () => qawafelConnector,
|
|
@@ -505,6 +513,7 @@ __export(adapters_exports, {
|
|
|
505
513
|
quadernoConnector: () => quadernoConnector,
|
|
506
514
|
quickbooksConnector: () => quickbooksConnector,
|
|
507
515
|
quickzuConnector: () => quickzuConnector,
|
|
516
|
+
rabbitMqConnector: () => rabbitMqConnector,
|
|
508
517
|
raiaAiConnector: () => raiaAiConnector,
|
|
509
518
|
rampConnector: () => rampConnector,
|
|
510
519
|
rapidtextAiConnector: () => rapidtextAiConnector,
|
|
@@ -512,6 +521,7 @@ __export(adapters_exports, {
|
|
|
512
521
|
recallAiConnector: () => recallAiConnector,
|
|
513
522
|
recurlyConnector: () => recurlyConnector,
|
|
514
523
|
redditConnector: () => redditConnector,
|
|
524
|
+
redisConnector: () => redisConnector,
|
|
515
525
|
reoonVerifierConnector: () => reoonVerifierConnector,
|
|
516
526
|
replyIoConnector: () => replyIoConnector,
|
|
517
527
|
resolveConnectorAdapterFactoryOptions: () => resolveConnectorAdapterFactoryOptions,
|
|
@@ -1120,6 +1130,10 @@ function sameUrl(left, right) {
|
|
|
1120
1130
|
function applyCredentials(headers, url, placement, credentials) {
|
|
1121
1131
|
const token = credentialToken(credentials);
|
|
1122
1132
|
if (placement.kind === "bearer") headers.authorization = `Bearer ${token}`;
|
|
1133
|
+
if (placement.kind === "authorization-by-auth") {
|
|
1134
|
+
const prefix = credentials.kind === "oauth2" ? placement.oauth2Prefix ?? "Bearer " : placement.apiKeyPrefix ?? "";
|
|
1135
|
+
headers.authorization = `${prefix}${token}`;
|
|
1136
|
+
}
|
|
1123
1137
|
if (placement.kind === "basic-api-key") {
|
|
1124
1138
|
headers.authorization = `Basic ${Buffer.from(`${token}:`).toString("base64")}`;
|
|
1125
1139
|
}
|
|
@@ -6497,6 +6511,294 @@ var digitalPilotConnector = declarativeRestConnector({
|
|
|
6497
6511
|
]
|
|
6498
6512
|
});
|
|
6499
6513
|
|
|
6514
|
+
// src/connectors/adapters/duckdb.ts
|
|
6515
|
+
import {
|
|
6516
|
+
DuckDBInstance
|
|
6517
|
+
} from "@duckdb/node-api";
|
|
6518
|
+
var MAX_ARGUMENTS = 100;
|
|
6519
|
+
var MAX_COLUMNS2 = 256;
|
|
6520
|
+
var MAX_EXECUTION_MS = 5e3;
|
|
6521
|
+
var MAX_INPUT_BYTES = 10 * 1024 * 1024;
|
|
6522
|
+
var MAX_QUERY_BYTES = 2e4;
|
|
6523
|
+
var MAX_RESULT_BYTES = 10 * 1024 * 1024;
|
|
6524
|
+
var MAX_ROWS = 1e4;
|
|
6525
|
+
var MAX_SCHEMA_DEPTH = 16;
|
|
6526
|
+
var MAX_TABLES = 16;
|
|
6527
|
+
var TABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,62}$/;
|
|
6528
|
+
var duckdbConnector = {
|
|
6529
|
+
manifest: {
|
|
6530
|
+
kind: "duckdb",
|
|
6531
|
+
displayName: "DuckDB",
|
|
6532
|
+
description: "Load bounded JSON tables into a throwaway in-memory DuckDB database and run a parameterized query.",
|
|
6533
|
+
auth: { kind: "none" },
|
|
6534
|
+
defaultConsistencyModel: "advisory",
|
|
6535
|
+
category: "database",
|
|
6536
|
+
capabilities: [
|
|
6537
|
+
{
|
|
6538
|
+
name: "create.and.query.db",
|
|
6539
|
+
class: "read",
|
|
6540
|
+
description: "Create in-memory tables from JSON arrays and return a bounded parameterized SQL query result.",
|
|
6541
|
+
parameters: {
|
|
6542
|
+
type: "object",
|
|
6543
|
+
properties: {
|
|
6544
|
+
tables: {
|
|
6545
|
+
type: "array",
|
|
6546
|
+
minItems: 1,
|
|
6547
|
+
maxItems: MAX_TABLES,
|
|
6548
|
+
items: {
|
|
6549
|
+
type: "object",
|
|
6550
|
+
properties: {
|
|
6551
|
+
name: { type: "string", pattern: TABLE_NAME.source, maxLength: 63 },
|
|
6552
|
+
data: { type: "array", maxItems: MAX_ROWS, items: { type: "object" } },
|
|
6553
|
+
schema: { type: "object", maxProperties: MAX_COLUMNS2 }
|
|
6554
|
+
},
|
|
6555
|
+
required: ["name", "data"],
|
|
6556
|
+
additionalProperties: false
|
|
6557
|
+
}
|
|
6558
|
+
},
|
|
6559
|
+
query: {
|
|
6560
|
+
type: "string",
|
|
6561
|
+
minLength: 1,
|
|
6562
|
+
maxLength: MAX_QUERY_BYTES,
|
|
6563
|
+
description: "A single relation-returning SQL query. Use $1, $2, and args for dynamic values."
|
|
6564
|
+
},
|
|
6565
|
+
args: {
|
|
6566
|
+
type: "array",
|
|
6567
|
+
maxItems: MAX_ARGUMENTS,
|
|
6568
|
+
items: { type: ["string", "number", "boolean", "null"] }
|
|
6569
|
+
},
|
|
6570
|
+
maxRows: { type: "integer", minimum: 1, maximum: MAX_ROWS, default: 1e3 }
|
|
6571
|
+
},
|
|
6572
|
+
required: ["tables", "query"],
|
|
6573
|
+
additionalProperties: false
|
|
6574
|
+
}
|
|
6575
|
+
}
|
|
6576
|
+
]
|
|
6577
|
+
},
|
|
6578
|
+
async executeRead({ capabilityName, args }) {
|
|
6579
|
+
if (capabilityName !== "create.and.query.db") {
|
|
6580
|
+
throw new Error(`Unknown DuckDB capability: ${capabilityName}`);
|
|
6581
|
+
}
|
|
6582
|
+
const tables = readTables(args.tables);
|
|
6583
|
+
const query2 = readQuery(args.query);
|
|
6584
|
+
const queryArgs = readArguments(args.args);
|
|
6585
|
+
const maxRows = readBoundedInteger(args.maxRows, 1e3, 1, MAX_ROWS, "maxRows");
|
|
6586
|
+
const instance = await DuckDBInstance.create(":memory:");
|
|
6587
|
+
let connection;
|
|
6588
|
+
let timedOut = false;
|
|
6589
|
+
let timeout;
|
|
6590
|
+
try {
|
|
6591
|
+
const activeConnection = await instance.connect();
|
|
6592
|
+
connection = activeConnection;
|
|
6593
|
+
timeout = setTimeout(() => {
|
|
6594
|
+
timedOut = true;
|
|
6595
|
+
activeConnection.interrupt();
|
|
6596
|
+
}, MAX_EXECUTION_MS);
|
|
6597
|
+
await configureConnection(activeConnection);
|
|
6598
|
+
throwIfTimedOut(timedOut);
|
|
6599
|
+
for (const table of tables) {
|
|
6600
|
+
await loadTable(activeConnection, table);
|
|
6601
|
+
throwIfTimedOut(timedOut);
|
|
6602
|
+
}
|
|
6603
|
+
const reader = await activeConnection.runAndReadAll(
|
|
6604
|
+
`SELECT * FROM (${query2}) AS "_tangle_result" LIMIT ${maxRows + 1}`,
|
|
6605
|
+
queryArgs
|
|
6606
|
+
);
|
|
6607
|
+
throwIfTimedOut(timedOut);
|
|
6608
|
+
const allRows = reader.getRowObjectsJson();
|
|
6609
|
+
const truncated = allRows.length > maxRows;
|
|
6610
|
+
const rows = truncated ? allRows.slice(0, maxRows) : allRows;
|
|
6611
|
+
assertResultSize(rows);
|
|
6612
|
+
return {
|
|
6613
|
+
data: {
|
|
6614
|
+
rows,
|
|
6615
|
+
rowCount: rows.length,
|
|
6616
|
+
truncated,
|
|
6617
|
+
columns: reader.columnNameAndTypeObjectsJson()
|
|
6618
|
+
},
|
|
6619
|
+
fetchedAt: Date.now()
|
|
6620
|
+
};
|
|
6621
|
+
} catch (error) {
|
|
6622
|
+
if (timedOut) throw new Error(`DuckDB execution exceeded ${MAX_EXECUTION_MS}ms`);
|
|
6623
|
+
throw error;
|
|
6624
|
+
} finally {
|
|
6625
|
+
if (timeout) clearTimeout(timeout);
|
|
6626
|
+
try {
|
|
6627
|
+
connection?.closeSync();
|
|
6628
|
+
} finally {
|
|
6629
|
+
instance.closeSync();
|
|
6630
|
+
}
|
|
6631
|
+
}
|
|
6632
|
+
},
|
|
6633
|
+
async test() {
|
|
6634
|
+
const instance = await DuckDBInstance.create(":memory:");
|
|
6635
|
+
let connection;
|
|
6636
|
+
try {
|
|
6637
|
+
const activeConnection = await instance.connect();
|
|
6638
|
+
connection = activeConnection;
|
|
6639
|
+
await configureConnection(activeConnection);
|
|
6640
|
+
const reader = await activeConnection.runAndReadAll("SELECT 1 AS ok");
|
|
6641
|
+
if (reader.getRowObjectsJson()[0]?.ok !== 1) return { ok: false, reason: "DuckDB SELECT 1 returned an unexpected result" };
|
|
6642
|
+
return { ok: true };
|
|
6643
|
+
} catch (error) {
|
|
6644
|
+
return { ok: false, reason: error instanceof Error ? error.message : String(error) };
|
|
6645
|
+
} finally {
|
|
6646
|
+
try {
|
|
6647
|
+
connection?.closeSync();
|
|
6648
|
+
} finally {
|
|
6649
|
+
instance.closeSync();
|
|
6650
|
+
}
|
|
6651
|
+
}
|
|
6652
|
+
}
|
|
6653
|
+
};
|
|
6654
|
+
async function configureConnection(connection) {
|
|
6655
|
+
await connection.run(`
|
|
6656
|
+
SET memory_limit = '256MB';
|
|
6657
|
+
SET max_temp_directory_size = '0B';
|
|
6658
|
+
SET threads = 2;
|
|
6659
|
+
SET enable_logging = false;
|
|
6660
|
+
SET enable_external_access = false;
|
|
6661
|
+
SET lock_configuration = true;
|
|
6662
|
+
`);
|
|
6663
|
+
}
|
|
6664
|
+
async function loadTable(connection, table) {
|
|
6665
|
+
const sourceData = stringifyJson(table.data, `${table.name}.data`);
|
|
6666
|
+
let sourceSchema;
|
|
6667
|
+
if (table.schema) {
|
|
6668
|
+
assertDuckDbSchema(table.schema, `${table.name}.schema`);
|
|
6669
|
+
sourceSchema = stringifyJson([table.schema], `${table.name}.schema`);
|
|
6670
|
+
} else {
|
|
6671
|
+
if (table.data.length === 0) {
|
|
6672
|
+
throw new Error(`${table.name}.schema is required when data is empty`);
|
|
6673
|
+
}
|
|
6674
|
+
const detected = await connection.runAndReadAll(
|
|
6675
|
+
"SELECT json_structure($sourceData) AS schema",
|
|
6676
|
+
{ sourceData }
|
|
6677
|
+
);
|
|
6678
|
+
const value = detected.getRowsJson()[0]?.[0];
|
|
6679
|
+
if (typeof value !== "string") throw new Error(`Could not infer a schema for ${table.name}`);
|
|
6680
|
+
let detectedSchema;
|
|
6681
|
+
try {
|
|
6682
|
+
detectedSchema = JSON.parse(value);
|
|
6683
|
+
} catch {
|
|
6684
|
+
throw new Error(`Could not parse the inferred schema for ${table.name}`);
|
|
6685
|
+
}
|
|
6686
|
+
assertDuckDbSchema(detectedSchema, `${table.name}.inferredSchema`);
|
|
6687
|
+
sourceSchema = value;
|
|
6688
|
+
}
|
|
6689
|
+
await connection.run(
|
|
6690
|
+
`CREATE TABLE ${quoteIdentifier(table.name)} AS
|
|
6691
|
+
SELECT UNNEST(JSON_TRANSFORM($sourceData, $sourceSchema), recursive := true)`,
|
|
6692
|
+
{ sourceData, sourceSchema }
|
|
6693
|
+
);
|
|
6694
|
+
}
|
|
6695
|
+
function readTables(value) {
|
|
6696
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > MAX_TABLES) {
|
|
6697
|
+
throw new Error(`tables must contain 1 through ${MAX_TABLES} entries`);
|
|
6698
|
+
}
|
|
6699
|
+
assertInputSize(value);
|
|
6700
|
+
let totalRows = 0;
|
|
6701
|
+
const tables = value.map((entry, tableIndex) => {
|
|
6702
|
+
if (!isPlainRecord(entry)) throw new Error(`tables[${tableIndex}] must be an object`);
|
|
6703
|
+
const { name, data: data4, schema } = entry;
|
|
6704
|
+
if (typeof name !== "string" || !TABLE_NAME.test(name)) {
|
|
6705
|
+
throw new Error(`tables[${tableIndex}].name must be a safe SQL identifier`);
|
|
6706
|
+
}
|
|
6707
|
+
if (!Array.isArray(data4) || data4.length > MAX_ROWS) {
|
|
6708
|
+
throw new Error(`tables[${tableIndex}].data must contain at most ${MAX_ROWS} rows`);
|
|
6709
|
+
}
|
|
6710
|
+
const records = data4.map((record, rowIndex) => {
|
|
6711
|
+
if (!isPlainRecord(record)) throw new Error(`tables[${tableIndex}].data[${rowIndex}] must be an object`);
|
|
6712
|
+
if (Object.keys(record).length > MAX_COLUMNS2) {
|
|
6713
|
+
throw new Error(`tables[${tableIndex}].data[${rowIndex}] exceeds ${MAX_COLUMNS2} columns`);
|
|
6714
|
+
}
|
|
6715
|
+
return record;
|
|
6716
|
+
});
|
|
6717
|
+
const columns = new Set(records.flatMap((record) => Object.keys(record)));
|
|
6718
|
+
if (columns.size > MAX_COLUMNS2) {
|
|
6719
|
+
throw new Error(`tables[${tableIndex}].data exceeds ${MAX_COLUMNS2} distinct columns`);
|
|
6720
|
+
}
|
|
6721
|
+
totalRows += records.length;
|
|
6722
|
+
if (totalRows > MAX_ROWS) throw new Error(`tables exceed the ${MAX_ROWS}-row total limit`);
|
|
6723
|
+
if (schema !== void 0 && (!isPlainRecord(schema) || Object.keys(schema).length > MAX_COLUMNS2)) {
|
|
6724
|
+
throw new Error(`tables[${tableIndex}].schema must contain at most ${MAX_COLUMNS2} fields`);
|
|
6725
|
+
}
|
|
6726
|
+
return { name, data: records, schema };
|
|
6727
|
+
});
|
|
6728
|
+
if (new Set(tables.map((table) => table.name.toLowerCase())).size !== tables.length) {
|
|
6729
|
+
throw new Error("table names must be unique ignoring case");
|
|
6730
|
+
}
|
|
6731
|
+
return tables;
|
|
6732
|
+
}
|
|
6733
|
+
function readQuery(value) {
|
|
6734
|
+
if (typeof value !== "string" || value.trim().length === 0) throw new Error("query is required");
|
|
6735
|
+
if (Buffer.byteLength(value, "utf8") > MAX_QUERY_BYTES) {
|
|
6736
|
+
throw new Error(`query exceeds the ${MAX_QUERY_BYTES}-byte limit`);
|
|
6737
|
+
}
|
|
6738
|
+
return value.trim().replace(/;\s*$/, "");
|
|
6739
|
+
}
|
|
6740
|
+
function readArguments(value) {
|
|
6741
|
+
if (value === void 0 || value === null) return {};
|
|
6742
|
+
if (!Array.isArray(value) || value.length > MAX_ARGUMENTS) {
|
|
6743
|
+
throw new Error(`args must contain at most ${MAX_ARGUMENTS} scalar values`);
|
|
6744
|
+
}
|
|
6745
|
+
return Object.fromEntries(value.map((entry, index) => {
|
|
6746
|
+
if (entry !== null && typeof entry !== "string" && typeof entry !== "number" && typeof entry !== "boolean") {
|
|
6747
|
+
throw new Error(`args[${index}] must be a string, number, boolean, or null`);
|
|
6748
|
+
}
|
|
6749
|
+
if (typeof entry === "number" && !Number.isFinite(entry)) throw new Error(`args[${index}] must be finite`);
|
|
6750
|
+
return [String(index + 1), entry];
|
|
6751
|
+
}));
|
|
6752
|
+
}
|
|
6753
|
+
function assertInputSize(value) {
|
|
6754
|
+
const serialized = stringifyJson(value, "tables");
|
|
6755
|
+
if (Buffer.byteLength(serialized, "utf8") > MAX_INPUT_BYTES) {
|
|
6756
|
+
throw new Error(`tables exceed the ${MAX_INPUT_BYTES}-byte input limit`);
|
|
6757
|
+
}
|
|
6758
|
+
}
|
|
6759
|
+
function assertResultSize(rows) {
|
|
6760
|
+
const serialized = stringifyJson(rows, "DuckDB result");
|
|
6761
|
+
if (Buffer.byteLength(serialized, "utf8") > MAX_RESULT_BYTES) {
|
|
6762
|
+
throw new Error(`DuckDB result exceeds the ${MAX_RESULT_BYTES}-byte output limit`);
|
|
6763
|
+
}
|
|
6764
|
+
}
|
|
6765
|
+
function assertDuckDbSchema(value, label) {
|
|
6766
|
+
let leaves = 0;
|
|
6767
|
+
const visit = (entry, depth) => {
|
|
6768
|
+
if (depth > MAX_SCHEMA_DEPTH) throw new Error(`${label} exceeds ${MAX_SCHEMA_DEPTH} nested levels`);
|
|
6769
|
+
if (typeof entry === "string") {
|
|
6770
|
+
leaves += 1;
|
|
6771
|
+
if (leaves > MAX_COLUMNS2) throw new Error(`${label} exceeds ${MAX_COLUMNS2} leaf columns`);
|
|
6772
|
+
return;
|
|
6773
|
+
}
|
|
6774
|
+
if (Array.isArray(entry)) {
|
|
6775
|
+
if (entry.length !== 1) throw new Error(`${label} array schemas must contain exactly one element type`);
|
|
6776
|
+
visit(entry[0], depth + 1);
|
|
6777
|
+
return;
|
|
6778
|
+
}
|
|
6779
|
+
if (!isPlainRecord(entry) || Object.keys(entry).length === 0) {
|
|
6780
|
+
throw new Error(`${label} must describe at least one DuckDB column type`);
|
|
6781
|
+
}
|
|
6782
|
+
for (const child of Object.values(entry)) visit(child, depth + 1);
|
|
6783
|
+
};
|
|
6784
|
+
visit(value, 0);
|
|
6785
|
+
}
|
|
6786
|
+
function stringifyJson(value, label) {
|
|
6787
|
+
try {
|
|
6788
|
+
const serialized = JSON.stringify(value);
|
|
6789
|
+
if (serialized === void 0) throw new Error("undefined");
|
|
6790
|
+
return serialized;
|
|
6791
|
+
} catch {
|
|
6792
|
+
throw new Error(`${label} must be JSON-serializable`);
|
|
6793
|
+
}
|
|
6794
|
+
}
|
|
6795
|
+
function quoteIdentifier(value) {
|
|
6796
|
+
return `"${value.replaceAll('"', '""')}"`;
|
|
6797
|
+
}
|
|
6798
|
+
function throwIfTimedOut(timedOut) {
|
|
6799
|
+
if (timedOut) throw new Error(`DuckDB execution exceeded ${MAX_EXECUTION_MS}ms`);
|
|
6800
|
+
}
|
|
6801
|
+
|
|
6500
6802
|
// src/connectors/adapters/docsbot.ts
|
|
6501
6803
|
var docsbotConnector = declarativeRestConnector({
|
|
6502
6804
|
kind: "docsbot",
|
|
@@ -6914,7 +7216,7 @@ var elevenlabsConnector = declarativeRestConnector({
|
|
|
6914
7216
|
// src/connectors/adapters/excel-files.ts
|
|
6915
7217
|
import readXlsxFile from "read-excel-file/node";
|
|
6916
7218
|
import writeXlsxFile from "write-excel-file/node";
|
|
6917
|
-
var
|
|
7219
|
+
var MAX_COLUMNS3 = 256;
|
|
6918
7220
|
var MAX_SHEETS = 50;
|
|
6919
7221
|
var MAX_UNCOMPRESSED_WORKBOOK_BYTES = 50 * 1024 * 1024;
|
|
6920
7222
|
var MAX_ZIP_ENTRIES = 1e3;
|
|
@@ -6955,7 +7257,7 @@ var excelFilesConnector = {
|
|
|
6955
7257
|
type: "array",
|
|
6956
7258
|
minItems: 1,
|
|
6957
7259
|
maxItems: MAX_RECORDS,
|
|
6958
|
-
items: { type: "array", maxItems:
|
|
7260
|
+
items: { type: "array", maxItems: MAX_COLUMNS3 }
|
|
6959
7261
|
},
|
|
6960
7262
|
sheetName: { type: "string", minLength: 1, maxLength: 31, default: "Sheet1" }
|
|
6961
7263
|
},
|
|
@@ -6985,8 +7287,8 @@ var excelFilesConnector = {
|
|
|
6985
7287
|
totalRows += sheet.data.length;
|
|
6986
7288
|
if (totalRows > MAX_RECORDS) throw new Error(`workbook exceeds the ${MAX_RECORDS}-row limit`);
|
|
6987
7289
|
for (const [rowIndex, row] of sheet.data.entries()) {
|
|
6988
|
-
if (row.length >
|
|
6989
|
-
throw new Error(`sheet ${sheet.sheet} row ${rowIndex + 1} exceeds the ${
|
|
7290
|
+
if (row.length > MAX_COLUMNS3) {
|
|
7291
|
+
throw new Error(`sheet ${sheet.sheet} row ${rowIndex + 1} exceeds the ${MAX_COLUMNS3}-column limit`);
|
|
6990
7292
|
}
|
|
6991
7293
|
}
|
|
6992
7294
|
return { name: sheet.sheet, rows: jsonSafe(sheet.data) };
|
|
@@ -7000,7 +7302,7 @@ var excelFilesConnector = {
|
|
|
7000
7302
|
if (capabilityName !== "excel.create") throw new Error(`Unknown Excel capability: ${capabilityName}`);
|
|
7001
7303
|
const rows = readRows(args.rows);
|
|
7002
7304
|
for (const [index, row] of rows.entries()) {
|
|
7003
|
-
if (row.length >
|
|
7305
|
+
if (row.length > MAX_COLUMNS3) throw new Error(`rows[${index}] exceeds the ${MAX_COLUMNS3}-column limit`);
|
|
7004
7306
|
}
|
|
7005
7307
|
const sheetName = readOptionalString(args.sheetName, "sheetName") ?? "Sheet1";
|
|
7006
7308
|
validateSheetName(sheetName);
|
|
@@ -16298,16 +16600,27 @@ var clickupConnector = declarativeRestConnector({
|
|
|
16298
16600
|
displayName: "ClickUp",
|
|
16299
16601
|
description: "Read and mutate ClickUp workspaces, spaces, folders, lists, tasks, comments, and time entries via the ClickUp REST v2 API.",
|
|
16300
16602
|
auth: {
|
|
16301
|
-
kind: "
|
|
16302
|
-
|
|
16303
|
-
|
|
16304
|
-
|
|
16305
|
-
|
|
16306
|
-
|
|
16603
|
+
kind: "one_of",
|
|
16604
|
+
preferred: "api-key",
|
|
16605
|
+
options: [
|
|
16606
|
+
{
|
|
16607
|
+
kind: "api-key",
|
|
16608
|
+
hint: "ClickUp personal API token (starts with pk_)."
|
|
16609
|
+
},
|
|
16610
|
+
{
|
|
16611
|
+
kind: "oauth2",
|
|
16612
|
+
authorizationUrl: "https://app.clickup.com/api",
|
|
16613
|
+
tokenUrl: "https://api.clickup.com/api/v2/oauth/token",
|
|
16614
|
+
scopes: [],
|
|
16615
|
+
clientIdEnv: "CLICKUP_OAUTH_CLIENT_ID",
|
|
16616
|
+
clientSecretEnv: "CLICKUP_OAUTH_CLIENT_SECRET"
|
|
16617
|
+
}
|
|
16618
|
+
]
|
|
16307
16619
|
},
|
|
16308
16620
|
category: "other",
|
|
16309
16621
|
defaultConsistencyModel: "authoritative",
|
|
16310
16622
|
baseUrl: "https://api.clickup.com/api/v2",
|
|
16623
|
+
credentialPlacement: { kind: "authorization-by-auth" },
|
|
16311
16624
|
// GET /user — returns the authorized user; used as a cheap liveness probe.
|
|
16312
16625
|
test: { method: "GET", path: "/user" },
|
|
16313
16626
|
capabilities: [
|
|
@@ -33157,6 +33470,548 @@ var justcallConnector = declarativeRestConnector({
|
|
|
33157
33470
|
]
|
|
33158
33471
|
});
|
|
33159
33472
|
|
|
33473
|
+
// src/connectors/adapters/kafka.ts
|
|
33474
|
+
import { isIP as isIP2 } from "net";
|
|
33475
|
+
import tls from "tls";
|
|
33476
|
+
import {
|
|
33477
|
+
Kafka,
|
|
33478
|
+
logLevel
|
|
33479
|
+
} from "kafkajs";
|
|
33480
|
+
|
|
33481
|
+
// src/connectors/adapters/public-network.ts
|
|
33482
|
+
import { lookup } from "dns/promises";
|
|
33483
|
+
import ipaddr from "ipaddr.js";
|
|
33484
|
+
function isPublicNetworkAddress(value) {
|
|
33485
|
+
if (!ipaddr.isValid(value)) return false;
|
|
33486
|
+
let address = ipaddr.parse(value);
|
|
33487
|
+
if (address.kind() === "ipv6" && address.isIPv4MappedAddress()) {
|
|
33488
|
+
address = address.toIPv4Address();
|
|
33489
|
+
}
|
|
33490
|
+
return address.range() === "unicast";
|
|
33491
|
+
}
|
|
33492
|
+
async function resolvePublicHostAddresses(host) {
|
|
33493
|
+
const addresses = ipaddr.isValid(host) ? [host] : (await lookup(host, { all: true, verbatim: true })).map((entry) => entry.address);
|
|
33494
|
+
if (addresses.length === 0 || addresses.some((address) => !isPublicNetworkAddress(address))) {
|
|
33495
|
+
throw new Error("host is not a public network target");
|
|
33496
|
+
}
|
|
33497
|
+
return addresses;
|
|
33498
|
+
}
|
|
33499
|
+
var publicDnsLookup = (hostname, options, callback) => {
|
|
33500
|
+
const normalizedOptions = typeof options === "number" ? { family: options } : options;
|
|
33501
|
+
lookup(hostname, { ...normalizedOptions, all: true, verbatim: true }).then((entries) => {
|
|
33502
|
+
if (entries.length === 0 || entries.some((entry) => !isPublicNetworkAddress(entry.address))) {
|
|
33503
|
+
callback(new Error("host is not a public network target"), "", 0);
|
|
33504
|
+
return;
|
|
33505
|
+
}
|
|
33506
|
+
if (normalizedOptions?.all) {
|
|
33507
|
+
callback(null, entries, void 0);
|
|
33508
|
+
return;
|
|
33509
|
+
}
|
|
33510
|
+
callback(null, entries[0].address, entries[0].family);
|
|
33511
|
+
}).catch((error) => callback(error instanceof Error ? error : new Error("DNS lookup failed"), "", 0));
|
|
33512
|
+
};
|
|
33513
|
+
|
|
33514
|
+
// src/connectors/adapters/kafka.ts
|
|
33515
|
+
var MAX_TOPICS = 100;
|
|
33516
|
+
var MAX_MESSAGES = 100;
|
|
33517
|
+
var MAX_MESSAGE_BYTES2 = 1024 * 1024;
|
|
33518
|
+
var MAX_BATCH_BYTES2 = MAX_FILE_BYTES;
|
|
33519
|
+
var MAX_HEADERS = 64;
|
|
33520
|
+
var MAX_POLL_MS = 3e4;
|
|
33521
|
+
function createKafkaConnector(options = {}) {
|
|
33522
|
+
const createClient = options.createClient ?? ((config) => new Kafka(config));
|
|
33523
|
+
return {
|
|
33524
|
+
manifest: {
|
|
33525
|
+
kind: "kafka",
|
|
33526
|
+
displayName: "Apache Kafka",
|
|
33527
|
+
description: "Discover topics and groups, inspect offsets, produce records, and run bounded approved consumers over TLS.",
|
|
33528
|
+
auth: {
|
|
33529
|
+
kind: "api-key",
|
|
33530
|
+
hint: "JSON with brokers and optional SASL or mutual-TLS credentials. TLS is mandatory and broker addresses must resolve publicly."
|
|
33531
|
+
},
|
|
33532
|
+
defaultConsistencyModel: "advisory",
|
|
33533
|
+
category: "webhook",
|
|
33534
|
+
rateLimit: { requests: 120, windowMs: 6e4, scope: "data-source" },
|
|
33535
|
+
capabilities: [
|
|
33536
|
+
readCapability5("kafka.topics.list", "List Kafka topics visible to the connection.", emptySchema()),
|
|
33537
|
+
readCapability5("kafka.topics.describe", "Read partition and replica metadata for selected topics.", {
|
|
33538
|
+
type: "object",
|
|
33539
|
+
properties: { topics: topicArraySchema() },
|
|
33540
|
+
required: ["topics"],
|
|
33541
|
+
additionalProperties: false
|
|
33542
|
+
}),
|
|
33543
|
+
readCapability5("kafka.offsets.list", "Read low, high, and current offsets for one topic.", topicSchema()),
|
|
33544
|
+
readCapability5("kafka.groups.list", "List consumer groups visible to the connection.", emptySchema()),
|
|
33545
|
+
readCapability5("kafka.group.offsets", "Read committed offsets for a consumer group.", {
|
|
33546
|
+
type: "object",
|
|
33547
|
+
properties: {
|
|
33548
|
+
groupId: { type: "string", minLength: 1, maxLength: 255 },
|
|
33549
|
+
topics: topicArraySchema()
|
|
33550
|
+
},
|
|
33551
|
+
required: ["groupId"],
|
|
33552
|
+
additionalProperties: false
|
|
33553
|
+
}),
|
|
33554
|
+
mutationCapability5("kafka.messages.produce", "Produce one or more records with all acknowledgements enabled.", {
|
|
33555
|
+
type: "object",
|
|
33556
|
+
properties: {
|
|
33557
|
+
topic: topicNameSchema(),
|
|
33558
|
+
encoding: { type: "string", enum: ["utf8", "base64"], default: "utf8" },
|
|
33559
|
+
messages: {
|
|
33560
|
+
type: "array",
|
|
33561
|
+
minItems: 1,
|
|
33562
|
+
maxItems: MAX_MESSAGES,
|
|
33563
|
+
items: {
|
|
33564
|
+
type: "object",
|
|
33565
|
+
properties: {
|
|
33566
|
+
value: { type: "string" },
|
|
33567
|
+
key: { type: "string" },
|
|
33568
|
+
partition: { type: "integer", minimum: 0 },
|
|
33569
|
+
headers: { type: "object", additionalProperties: { type: "string" }, maxProperties: MAX_HEADERS }
|
|
33570
|
+
},
|
|
33571
|
+
required: ["value"],
|
|
33572
|
+
additionalProperties: false
|
|
33573
|
+
}
|
|
33574
|
+
}
|
|
33575
|
+
},
|
|
33576
|
+
required: ["topic", "messages"],
|
|
33577
|
+
additionalProperties: false
|
|
33578
|
+
}),
|
|
33579
|
+
mutationCapability5("kafka.messages.consume", "Join a consumer group and return a bounded batch without automatically committing offsets.", {
|
|
33580
|
+
type: "object",
|
|
33581
|
+
properties: {
|
|
33582
|
+
topic: topicNameSchema(),
|
|
33583
|
+
groupId: { type: "string", minLength: 1, maxLength: 255 },
|
|
33584
|
+
encoding: { type: "string", enum: ["utf8", "base64"], default: "utf8" },
|
|
33585
|
+
fromBeginning: { type: "boolean", default: false },
|
|
33586
|
+
limit: { type: "integer", minimum: 1, maximum: MAX_MESSAGES, default: 25 },
|
|
33587
|
+
timeoutMs: { type: "integer", minimum: 250, maximum: MAX_POLL_MS, default: 5e3 }
|
|
33588
|
+
},
|
|
33589
|
+
required: ["topic", "groupId"],
|
|
33590
|
+
additionalProperties: false
|
|
33591
|
+
}),
|
|
33592
|
+
mutationCapability5("kafka.offsets.commit", "Commit explicit next offsets for a consumer group.", {
|
|
33593
|
+
type: "object",
|
|
33594
|
+
properties: {
|
|
33595
|
+
groupId: { type: "string", minLength: 1, maxLength: 255 },
|
|
33596
|
+
offsets: {
|
|
33597
|
+
type: "array",
|
|
33598
|
+
minItems: 1,
|
|
33599
|
+
maxItems: MAX_MESSAGES,
|
|
33600
|
+
items: {
|
|
33601
|
+
type: "object",
|
|
33602
|
+
properties: {
|
|
33603
|
+
topic: topicNameSchema(),
|
|
33604
|
+
partition: { type: "integer", minimum: 0 },
|
|
33605
|
+
offset: { type: "string", pattern: "^[0-9]+$" },
|
|
33606
|
+
metadata: { type: "string", maxLength: 4096 }
|
|
33607
|
+
},
|
|
33608
|
+
required: ["topic", "partition", "offset"],
|
|
33609
|
+
additionalProperties: false
|
|
33610
|
+
}
|
|
33611
|
+
}
|
|
33612
|
+
},
|
|
33613
|
+
required: ["groupId", "offsets"],
|
|
33614
|
+
additionalProperties: false
|
|
33615
|
+
}),
|
|
33616
|
+
mutationCapability5("kafka.topics.create", "Create one Kafka topic with explicit partition and replication counts.", {
|
|
33617
|
+
type: "object",
|
|
33618
|
+
properties: {
|
|
33619
|
+
topic: topicNameSchema(),
|
|
33620
|
+
partitions: { type: "integer", minimum: 1, maximum: 100, default: 1 },
|
|
33621
|
+
replicationFactor: { type: "integer", minimum: 1, maximum: 10, default: 1 }
|
|
33622
|
+
},
|
|
33623
|
+
required: ["topic"],
|
|
33624
|
+
additionalProperties: false
|
|
33625
|
+
}),
|
|
33626
|
+
mutationCapability5("kafka.topics.delete", "Delete one or more Kafka topics.", {
|
|
33627
|
+
type: "object",
|
|
33628
|
+
properties: { topics: topicArraySchema() },
|
|
33629
|
+
required: ["topics"],
|
|
33630
|
+
additionalProperties: false
|
|
33631
|
+
})
|
|
33632
|
+
]
|
|
33633
|
+
},
|
|
33634
|
+
async executeRead({ source, capabilityName, args }) {
|
|
33635
|
+
const client = createClient(readCredentials(source).config);
|
|
33636
|
+
return withAdmin(client.admin(), async (admin) => {
|
|
33637
|
+
let data4;
|
|
33638
|
+
if (capabilityName === "kafka.topics.list") {
|
|
33639
|
+
data4 = { topics: await admin.listTopics() };
|
|
33640
|
+
} else if (capabilityName === "kafka.topics.describe") {
|
|
33641
|
+
data4 = await admin.fetchTopicMetadata({ topics: readTopics(args.topics) });
|
|
33642
|
+
} else if (capabilityName === "kafka.offsets.list") {
|
|
33643
|
+
const topic = readTopic(args.topic);
|
|
33644
|
+
data4 = { topic, partitions: await admin.fetchTopicOffsets(topic) };
|
|
33645
|
+
} else if (capabilityName === "kafka.groups.list") {
|
|
33646
|
+
data4 = await admin.listGroups();
|
|
33647
|
+
} else if (capabilityName === "kafka.group.offsets") {
|
|
33648
|
+
const groupId5 = readGroupId2(args.groupId);
|
|
33649
|
+
const topics = args.topics === void 0 ? void 0 : readTopics(args.topics);
|
|
33650
|
+
data4 = { groupId: groupId5, offsets: await admin.fetchOffsets({ groupId: groupId5, topics, resolveOffsets: true }) };
|
|
33651
|
+
} else {
|
|
33652
|
+
throw new Error(`Unknown Kafka read capability: ${capabilityName}`);
|
|
33653
|
+
}
|
|
33654
|
+
return { data: jsonSafe(data4), fetchedAt: Date.now() };
|
|
33655
|
+
});
|
|
33656
|
+
},
|
|
33657
|
+
async executeMutation({ source, capabilityName, args }) {
|
|
33658
|
+
const client = createClient(readCredentials(source).config);
|
|
33659
|
+
let data4;
|
|
33660
|
+
if (capabilityName === "kafka.messages.produce") {
|
|
33661
|
+
data4 = await produce(client.producer({ idempotent: true, maxInFlightRequests: 1 }), args);
|
|
33662
|
+
} else if (capabilityName === "kafka.messages.consume") {
|
|
33663
|
+
data4 = await consume(client.consumer({
|
|
33664
|
+
groupId: readGroupId2(args.groupId),
|
|
33665
|
+
allowAutoTopicCreation: false,
|
|
33666
|
+
maxBytes: MAX_BATCH_BYTES2,
|
|
33667
|
+
maxBytesPerPartition: MAX_BATCH_BYTES2
|
|
33668
|
+
}), args);
|
|
33669
|
+
} else if (capabilityName === "kafka.offsets.commit") {
|
|
33670
|
+
data4 = await commitOffsets(client.consumer({
|
|
33671
|
+
groupId: readGroupId2(args.groupId),
|
|
33672
|
+
allowAutoTopicCreation: false
|
|
33673
|
+
}), args);
|
|
33674
|
+
} else if (capabilityName === "kafka.topics.create") {
|
|
33675
|
+
data4 = await withAdmin(client.admin(), async (admin) => ({
|
|
33676
|
+
created: await admin.createTopics({
|
|
33677
|
+
waitForLeaders: true,
|
|
33678
|
+
topics: [{
|
|
33679
|
+
topic: readTopic(args.topic),
|
|
33680
|
+
numPartitions: readBoundedInteger(args.partitions, 1, 1, 100, "partitions"),
|
|
33681
|
+
replicationFactor: readBoundedInteger(args.replicationFactor, 1, 1, 10, "replicationFactor")
|
|
33682
|
+
}]
|
|
33683
|
+
})
|
|
33684
|
+
}));
|
|
33685
|
+
} else if (capabilityName === "kafka.topics.delete") {
|
|
33686
|
+
const topics = readTopics(args.topics);
|
|
33687
|
+
data4 = await withAdmin(client.admin(), async (admin) => {
|
|
33688
|
+
await admin.deleteTopics({ topics });
|
|
33689
|
+
return { topics };
|
|
33690
|
+
});
|
|
33691
|
+
} else {
|
|
33692
|
+
throw new Error(`Unknown Kafka mutation capability: ${capabilityName}`);
|
|
33693
|
+
}
|
|
33694
|
+
return {
|
|
33695
|
+
status: "committed",
|
|
33696
|
+
data: jsonSafe(data4),
|
|
33697
|
+
committedAt: Date.now(),
|
|
33698
|
+
idempotentReplay: false
|
|
33699
|
+
};
|
|
33700
|
+
},
|
|
33701
|
+
async test(source) {
|
|
33702
|
+
try {
|
|
33703
|
+
const admin = createClient(readCredentials(source).config).admin();
|
|
33704
|
+
await withAdmin(admin, (connected) => connected.listTopics());
|
|
33705
|
+
return { ok: true };
|
|
33706
|
+
} catch (error) {
|
|
33707
|
+
return { ok: false, reason: error instanceof Error ? error.message : "Kafka connection test failed" };
|
|
33708
|
+
}
|
|
33709
|
+
}
|
|
33710
|
+
};
|
|
33711
|
+
}
|
|
33712
|
+
var kafkaConnector = createKafkaConnector();
|
|
33713
|
+
var publicKafkaSocketFactory = ({ host, port, ssl, onConnect }) => {
|
|
33714
|
+
validateBrokerHost(host);
|
|
33715
|
+
if (isIP2(host) !== 0 && !isPublicNetworkAddress(host)) {
|
|
33716
|
+
throw new Error("Kafka broker is not a public network target");
|
|
33717
|
+
}
|
|
33718
|
+
const socket = tls.connect({
|
|
33719
|
+
...ssl,
|
|
33720
|
+
host,
|
|
33721
|
+
port,
|
|
33722
|
+
servername: isIP2(host) === 0 ? host : void 0,
|
|
33723
|
+
lookup: publicDnsLookup
|
|
33724
|
+
}, onConnect);
|
|
33725
|
+
socket.setKeepAlive(true, 6e4);
|
|
33726
|
+
return socket;
|
|
33727
|
+
};
|
|
33728
|
+
async function withAdmin(admin, run) {
|
|
33729
|
+
try {
|
|
33730
|
+
await admin.connect();
|
|
33731
|
+
return await run(admin);
|
|
33732
|
+
} finally {
|
|
33733
|
+
await admin.disconnect().catch(() => void 0);
|
|
33734
|
+
}
|
|
33735
|
+
}
|
|
33736
|
+
async function produce(producer, args) {
|
|
33737
|
+
const topic = readTopic(args.topic);
|
|
33738
|
+
const encoding = readEncoding(args.encoding);
|
|
33739
|
+
const messages = readMessageInputs(args.messages).map((message) => ({
|
|
33740
|
+
value: decodeMessageValue(message.value, encoding, "value"),
|
|
33741
|
+
key: message.key === void 0 ? void 0 : decodeMessageValue(message.key, encoding, "key"),
|
|
33742
|
+
partition: message.partition === void 0 ? void 0 : readBoundedInteger(message.partition, 0, 0, Number.MAX_SAFE_INTEGER, "partition"),
|
|
33743
|
+
headers: readHeaders(message.headers)
|
|
33744
|
+
}));
|
|
33745
|
+
const batchBytes = messages.reduce((total, message) => total + message.value.byteLength + (message.key?.byteLength ?? 0) + headerBytes(message.headers), 0);
|
|
33746
|
+
if (batchBytes > MAX_BATCH_BYTES2) throw new Error(`messages exceed the ${MAX_BATCH_BYTES2}-byte batch limit`);
|
|
33747
|
+
try {
|
|
33748
|
+
await producer.connect();
|
|
33749
|
+
return { topic, records: await producer.send({ topic, messages, acks: -1, timeout: 3e4 }) };
|
|
33750
|
+
} finally {
|
|
33751
|
+
await producer.disconnect().catch(() => void 0);
|
|
33752
|
+
}
|
|
33753
|
+
}
|
|
33754
|
+
async function consume(consumer, args) {
|
|
33755
|
+
const topic = readTopic(args.topic);
|
|
33756
|
+
const encoding = readEncoding(args.encoding);
|
|
33757
|
+
const limit = readBoundedInteger(args.limit, 25, 1, MAX_MESSAGES, "limit");
|
|
33758
|
+
const timeoutMs = readBoundedInteger(args.timeoutMs, 5e3, 250, MAX_POLL_MS, "timeoutMs");
|
|
33759
|
+
const messages = [];
|
|
33760
|
+
let batchBytes = 0;
|
|
33761
|
+
let resolveDone = () => void 0;
|
|
33762
|
+
let rejectDone = () => void 0;
|
|
33763
|
+
const done = new Promise((resolve, reject) => {
|
|
33764
|
+
resolveDone = resolve;
|
|
33765
|
+
rejectDone = reject;
|
|
33766
|
+
});
|
|
33767
|
+
let timer;
|
|
33768
|
+
try {
|
|
33769
|
+
await consumer.connect();
|
|
33770
|
+
await consumer.subscribe({ topics: [topic], fromBeginning: readBoolean(args.fromBeginning, false, "fromBeginning") });
|
|
33771
|
+
const run = consumer.run({
|
|
33772
|
+
autoCommit: false,
|
|
33773
|
+
partitionsConsumedConcurrently: 1,
|
|
33774
|
+
eachMessage: async ({ partition, message }) => {
|
|
33775
|
+
if (messages.length >= limit) return;
|
|
33776
|
+
const messageBytes = (message.key?.byteLength ?? 0) + (message.value?.byteLength ?? 0) + headerBytes(message.headers);
|
|
33777
|
+
if (messageBytes > MAX_MESSAGE_BYTES2) throw new Error(`Kafka message exceeds the ${MAX_MESSAGE_BYTES2}-byte limit`);
|
|
33778
|
+
batchBytes += messageBytes;
|
|
33779
|
+
if (batchBytes > MAX_BATCH_BYTES2) throw new Error(`Kafka batch exceeds the ${MAX_BATCH_BYTES2}-byte limit`);
|
|
33780
|
+
messages.push({
|
|
33781
|
+
topic,
|
|
33782
|
+
partition,
|
|
33783
|
+
offset: message.offset,
|
|
33784
|
+
timestamp: message.timestamp,
|
|
33785
|
+
key: encodeMessageValue(message.key, encoding),
|
|
33786
|
+
value: encodeMessageValue(message.value, encoding),
|
|
33787
|
+
headers: Object.fromEntries(Object.entries(message.headers ?? {}).map(([key, value]) => [key, encodeHeader(value, encoding)]))
|
|
33788
|
+
});
|
|
33789
|
+
if (messages.length >= limit) resolveDone();
|
|
33790
|
+
}
|
|
33791
|
+
});
|
|
33792
|
+
run.catch(rejectDone);
|
|
33793
|
+
timer = setTimeout(resolveDone, timeoutMs);
|
|
33794
|
+
await done;
|
|
33795
|
+
await consumer.stop();
|
|
33796
|
+
await run;
|
|
33797
|
+
return { topic, messages, messageCount: messages.length, offsetsCommitted: false };
|
|
33798
|
+
} finally {
|
|
33799
|
+
if (timer) clearTimeout(timer);
|
|
33800
|
+
await consumer.disconnect().catch(() => void 0);
|
|
33801
|
+
}
|
|
33802
|
+
}
|
|
33803
|
+
async function commitOffsets(consumer, args) {
|
|
33804
|
+
if (!Array.isArray(args.offsets) || args.offsets.length === 0 || args.offsets.length > MAX_MESSAGES) {
|
|
33805
|
+
throw new Error(`offsets must contain 1 through ${MAX_MESSAGES} entries`);
|
|
33806
|
+
}
|
|
33807
|
+
const offsets = args.offsets.map((entry, index) => {
|
|
33808
|
+
if (!isPlainRecord(entry)) throw new Error(`offsets[${index}] must be an object`);
|
|
33809
|
+
const offset = readOptionalString(entry.offset, `offsets[${index}].offset`);
|
|
33810
|
+
if (!offset || !/^[0-9]+$/.test(offset) || BigInt(offset) > 9223372036854775807n) {
|
|
33811
|
+
throw new Error(`offsets[${index}].offset must be a signed 64-bit non-negative integer string`);
|
|
33812
|
+
}
|
|
33813
|
+
return {
|
|
33814
|
+
topic: readTopic(entry.topic),
|
|
33815
|
+
partition: readBoundedInteger(entry.partition, 0, 0, Number.MAX_SAFE_INTEGER, `offsets[${index}].partition`),
|
|
33816
|
+
offset,
|
|
33817
|
+
metadata: readOptionalString(entry.metadata, `offsets[${index}].metadata`)
|
|
33818
|
+
};
|
|
33819
|
+
});
|
|
33820
|
+
try {
|
|
33821
|
+
await consumer.connect();
|
|
33822
|
+
await consumer.commitOffsets(offsets);
|
|
33823
|
+
return { groupId: readGroupId2(args.groupId), offsets };
|
|
33824
|
+
} finally {
|
|
33825
|
+
await consumer.disconnect().catch(() => void 0);
|
|
33826
|
+
}
|
|
33827
|
+
}
|
|
33828
|
+
function readCredentials(source) {
|
|
33829
|
+
let raw;
|
|
33830
|
+
if (source.credentials.kind === "custom") raw = source.credentials.values;
|
|
33831
|
+
else if (source.credentials.kind === "api-key") {
|
|
33832
|
+
try {
|
|
33833
|
+
raw = JSON.parse(source.credentials.apiKey);
|
|
33834
|
+
} catch {
|
|
33835
|
+
throw new Error("Kafka credential must be valid JSON");
|
|
33836
|
+
}
|
|
33837
|
+
} else {
|
|
33838
|
+
throw new Error("Kafka requires a structured credential bundle");
|
|
33839
|
+
}
|
|
33840
|
+
if (!isPlainRecord(raw)) throw new Error("Kafka credential must be a JSON object");
|
|
33841
|
+
const brokers = readBrokers(raw.brokers);
|
|
33842
|
+
const clientId = readOptionalString(raw.clientId, "clientId") ?? "tangle-integration-hub";
|
|
33843
|
+
if (!/^[A-Za-z0-9._-]{1,128}$/.test(clientId)) throw new Error("clientId contains unsupported characters");
|
|
33844
|
+
const sasl = readSasl(raw);
|
|
33845
|
+
const ca = readPemList(raw.tlsCa, "tlsCa");
|
|
33846
|
+
const cert = readOptionalString(raw.tlsCert, "tlsCert");
|
|
33847
|
+
const key = readOptionalString(raw.tlsKey, "tlsKey");
|
|
33848
|
+
if (Boolean(cert) !== Boolean(key)) throw new Error("tlsCert and tlsKey must be supplied together");
|
|
33849
|
+
return {
|
|
33850
|
+
config: {
|
|
33851
|
+
brokers,
|
|
33852
|
+
clientId,
|
|
33853
|
+
ssl: {
|
|
33854
|
+
minVersion: "TLSv1.2",
|
|
33855
|
+
rejectUnauthorized: true,
|
|
33856
|
+
ca,
|
|
33857
|
+
cert,
|
|
33858
|
+
key,
|
|
33859
|
+
passphrase: readOptionalString(raw.tlsPassphrase, "tlsPassphrase")
|
|
33860
|
+
},
|
|
33861
|
+
sasl,
|
|
33862
|
+
socketFactory: publicKafkaSocketFactory,
|
|
33863
|
+
connectionTimeout: 1e4,
|
|
33864
|
+
authenticationTimeout: 1e4,
|
|
33865
|
+
requestTimeout: 3e4,
|
|
33866
|
+
enforceRequestTimeout: true,
|
|
33867
|
+
retry: { retries: 3, maxRetryTime: 3e4 },
|
|
33868
|
+
logLevel: logLevel.NOTHING
|
|
33869
|
+
}
|
|
33870
|
+
};
|
|
33871
|
+
}
|
|
33872
|
+
function readBrokers(value) {
|
|
33873
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > 20) {
|
|
33874
|
+
throw new Error("brokers must contain 1 through 20 host:port entries");
|
|
33875
|
+
}
|
|
33876
|
+
return value.map((entry, index) => {
|
|
33877
|
+
if (typeof entry !== "string" || entry.length === 0) throw new Error(`brokers[${index}] must be a host:port string`);
|
|
33878
|
+
let parsed;
|
|
33879
|
+
try {
|
|
33880
|
+
parsed = new URL(`tcp://${entry}`);
|
|
33881
|
+
} catch {
|
|
33882
|
+
throw new Error(`brokers[${index}] is not a valid host:port`);
|
|
33883
|
+
}
|
|
33884
|
+
const host = parsed.hostname.replace(/^\[|\]$/g, "");
|
|
33885
|
+
if (!parsed.port || parsed.username || parsed.password || !["", "/"].includes(parsed.pathname)) {
|
|
33886
|
+
throw new Error(`brokers[${index}] must contain only host and port`);
|
|
33887
|
+
}
|
|
33888
|
+
validateBrokerHost(host);
|
|
33889
|
+
if (isIP2(host) !== 0 && !isPublicNetworkAddress(host)) throw new Error(`brokers[${index}] is not a public network target`);
|
|
33890
|
+
const port = Number(parsed.port);
|
|
33891
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`brokers[${index}] port is invalid`);
|
|
33892
|
+
return entry;
|
|
33893
|
+
});
|
|
33894
|
+
}
|
|
33895
|
+
function validateBrokerHost(host) {
|
|
33896
|
+
const normalized = host.toLowerCase();
|
|
33897
|
+
if (host.length === 0 || host.length > 253 || normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".local") || /[\s/@]/.test(host)) {
|
|
33898
|
+
throw new Error("Kafka broker must be a public hostname or IP address");
|
|
33899
|
+
}
|
|
33900
|
+
}
|
|
33901
|
+
function readSasl(raw) {
|
|
33902
|
+
if (raw.saslMechanism === void 0 || raw.saslMechanism === null) return void 0;
|
|
33903
|
+
if (!["plain", "scram-sha-256", "scram-sha-512"].includes(String(raw.saslMechanism))) {
|
|
33904
|
+
throw new Error("saslMechanism must be plain, scram-sha-256, or scram-sha-512");
|
|
33905
|
+
}
|
|
33906
|
+
const username = readOptionalString(raw.saslUsername, "saslUsername");
|
|
33907
|
+
const password = readOptionalString(raw.saslPassword, "saslPassword");
|
|
33908
|
+
if (!username || !password) throw new Error("SASL requires saslUsername and saslPassword");
|
|
33909
|
+
return { mechanism: raw.saslMechanism, username, password };
|
|
33910
|
+
}
|
|
33911
|
+
function readPemList(value, label) {
|
|
33912
|
+
if (value === void 0 || value === null) return void 0;
|
|
33913
|
+
const values = Array.isArray(value) ? value : [value];
|
|
33914
|
+
if (values.length === 0 || values.some((entry) => typeof entry !== "string" || entry.length === 0)) {
|
|
33915
|
+
throw new Error(`${label} must be a PEM string or non-empty array of PEM strings`);
|
|
33916
|
+
}
|
|
33917
|
+
return values;
|
|
33918
|
+
}
|
|
33919
|
+
function readTopics(value) {
|
|
33920
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > MAX_TOPICS) {
|
|
33921
|
+
throw new Error(`topics must contain 1 through ${MAX_TOPICS} names`);
|
|
33922
|
+
}
|
|
33923
|
+
const topics = value.map(readTopic);
|
|
33924
|
+
if (new Set(topics).size !== topics.length) throw new Error("topics must not contain duplicates");
|
|
33925
|
+
return topics;
|
|
33926
|
+
}
|
|
33927
|
+
function readTopic(value) {
|
|
33928
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9._-]{1,249}$/.test(value) || value === "." || value === "..") {
|
|
33929
|
+
throw new Error("topic must be 1 through 249 Kafka-safe characters");
|
|
33930
|
+
}
|
|
33931
|
+
return value;
|
|
33932
|
+
}
|
|
33933
|
+
function readGroupId2(value) {
|
|
33934
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 255 || /[\u0000-\u001f]/.test(value)) {
|
|
33935
|
+
throw new Error("groupId must be 1 through 255 printable characters");
|
|
33936
|
+
}
|
|
33937
|
+
return value;
|
|
33938
|
+
}
|
|
33939
|
+
function readEncoding(value) {
|
|
33940
|
+
if (value === void 0 || value === null || value === "utf8") return "utf8";
|
|
33941
|
+
if (value === "base64") return "base64";
|
|
33942
|
+
throw new Error("encoding must be utf8 or base64");
|
|
33943
|
+
}
|
|
33944
|
+
function readMessageInputs(value) {
|
|
33945
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > MAX_MESSAGES) {
|
|
33946
|
+
throw new Error(`messages must contain 1 through ${MAX_MESSAGES} entries`);
|
|
33947
|
+
}
|
|
33948
|
+
return value.map((entry, index) => {
|
|
33949
|
+
if (!isPlainRecord(entry)) throw new Error(`messages[${index}] must be an object`);
|
|
33950
|
+
return entry;
|
|
33951
|
+
});
|
|
33952
|
+
}
|
|
33953
|
+
function decodeMessageValue(value, encoding, label) {
|
|
33954
|
+
if (typeof value !== "string") throw new Error(`${label} must be a string`);
|
|
33955
|
+
if (encoding === "base64" && (value.length % 4 !== 0 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value))) {
|
|
33956
|
+
throw new Error(`${label} must be canonical base64`);
|
|
33957
|
+
}
|
|
33958
|
+
const decoded = Buffer.from(value, encoding);
|
|
33959
|
+
if (decoded.byteLength > MAX_MESSAGE_BYTES2) throw new Error(`${label} exceeds the ${MAX_MESSAGE_BYTES2}-byte limit`);
|
|
33960
|
+
return decoded;
|
|
33961
|
+
}
|
|
33962
|
+
function encodeMessageValue(value, encoding) {
|
|
33963
|
+
return value ? value.toString(encoding) : null;
|
|
33964
|
+
}
|
|
33965
|
+
function encodeHeader(value, encoding) {
|
|
33966
|
+
if (value === void 0) return void 0;
|
|
33967
|
+
if (Array.isArray(value)) return value.map((entry) => Buffer.isBuffer(entry) ? entry.toString(encoding) : entry);
|
|
33968
|
+
return Buffer.isBuffer(value) ? value.toString(encoding) : value;
|
|
33969
|
+
}
|
|
33970
|
+
function readHeaders(value) {
|
|
33971
|
+
if (value === void 0 || value === null) return void 0;
|
|
33972
|
+
if (!isPlainRecord(value) || Object.keys(value).length > MAX_HEADERS) throw new Error(`headers must have at most ${MAX_HEADERS} string fields`);
|
|
33973
|
+
const headers = Object.fromEntries(Object.entries(value).map(([key, entry]) => {
|
|
33974
|
+
if (typeof entry !== "string") throw new Error(`header ${key} must be a string`);
|
|
33975
|
+
return [key, entry];
|
|
33976
|
+
}));
|
|
33977
|
+
if (headerBytes(headers) > MAX_MESSAGE_BYTES2) throw new Error(`headers exceed the ${MAX_MESSAGE_BYTES2}-byte limit`);
|
|
33978
|
+
return headers;
|
|
33979
|
+
}
|
|
33980
|
+
function headerBytes(headers) {
|
|
33981
|
+
if (!headers) return 0;
|
|
33982
|
+
return Object.entries(headers).reduce((total, [key, value]) => {
|
|
33983
|
+
const values = Array.isArray(value) ? value : [value];
|
|
33984
|
+
return total + Buffer.byteLength(key, "utf8") + values.reduce((sum, entry) => {
|
|
33985
|
+
if (Buffer.isBuffer(entry)) return sum + entry.byteLength;
|
|
33986
|
+
if (typeof entry === "string") return sum + Buffer.byteLength(entry, "utf8");
|
|
33987
|
+
return sum;
|
|
33988
|
+
}, 0);
|
|
33989
|
+
}, 0);
|
|
33990
|
+
}
|
|
33991
|
+
function emptySchema() {
|
|
33992
|
+
return { type: "object", properties: {}, additionalProperties: false };
|
|
33993
|
+
}
|
|
33994
|
+
function topicSchema() {
|
|
33995
|
+
return {
|
|
33996
|
+
type: "object",
|
|
33997
|
+
properties: { topic: topicNameSchema() },
|
|
33998
|
+
required: ["topic"],
|
|
33999
|
+
additionalProperties: false
|
|
34000
|
+
};
|
|
34001
|
+
}
|
|
34002
|
+
function topicArraySchema() {
|
|
34003
|
+
return { type: "array", minItems: 1, maxItems: MAX_TOPICS, items: topicNameSchema() };
|
|
34004
|
+
}
|
|
34005
|
+
function topicNameSchema() {
|
|
34006
|
+
return { type: "string", minLength: 1, maxLength: 249, pattern: "^[A-Za-z0-9._-]+$" };
|
|
34007
|
+
}
|
|
34008
|
+
function readCapability5(name, description, parameters) {
|
|
34009
|
+
return { name, class: "read", description, parameters };
|
|
34010
|
+
}
|
|
34011
|
+
function mutationCapability5(name, description, parameters) {
|
|
34012
|
+
return { name, class: "mutation", description, parameters, cas: "none", externalEffect: true };
|
|
34013
|
+
}
|
|
34014
|
+
|
|
33160
34015
|
// src/connectors/adapters/lead-connector.ts
|
|
33161
34016
|
var leadConnectorConnector = declarativeRestConnector({
|
|
33162
34017
|
kind: "lead-connector",
|
|
@@ -43338,7 +44193,7 @@ var pipedriveConnector = declarativeRestConnector({
|
|
|
43338
44193
|
// src/connectors/adapters/parquet-files.ts
|
|
43339
44194
|
import { parquetReadObjects } from "hyparquet";
|
|
43340
44195
|
import { parquetWriteBuffer } from "hyparquet-writer";
|
|
43341
|
-
var
|
|
44196
|
+
var MAX_COLUMNS4 = 256;
|
|
43342
44197
|
var parquetFilesConnector = {
|
|
43343
44198
|
manifest: {
|
|
43344
44199
|
kind: "parquet-files",
|
|
@@ -43356,7 +44211,7 @@ var parquetFilesConnector = {
|
|
|
43356
44211
|
type: "object",
|
|
43357
44212
|
properties: {
|
|
43358
44213
|
fileBase64: { type: "string", description: "Parquet file content encoded as canonical base64." },
|
|
43359
|
-
columns: { type: "array", maxItems:
|
|
44214
|
+
columns: { type: "array", maxItems: MAX_COLUMNS4, items: { type: "string", minLength: 1 } },
|
|
43360
44215
|
rowStart: { type: "integer", minimum: 0, default: 0 },
|
|
43361
44216
|
rowLimit: { type: "integer", minimum: 1, maximum: MAX_RECORDS, default: MAX_RECORDS }
|
|
43362
44217
|
},
|
|
@@ -43402,8 +44257,8 @@ var parquetFilesConnector = {
|
|
|
43402
44257
|
if (capabilityName !== "parquet.create") throw new Error(`Unknown Parquet capability: ${capabilityName}`);
|
|
43403
44258
|
const records = readRecords(args.records);
|
|
43404
44259
|
const columnNames = [...new Set(records.flatMap((record) => Object.keys(record)))];
|
|
43405
|
-
if (columnNames.length === 0 || columnNames.length >
|
|
43406
|
-
throw new Error(`records must contain 1 through ${
|
|
44260
|
+
if (columnNames.length === 0 || columnNames.length > MAX_COLUMNS4) {
|
|
44261
|
+
throw new Error(`records must contain 1 through ${MAX_COLUMNS4} columns`);
|
|
43407
44262
|
}
|
|
43408
44263
|
if (columnNames.some((name) => name.length === 0)) throw new Error("column names must not be empty");
|
|
43409
44264
|
const columnData = columnNames.map((name) => {
|
|
@@ -43433,8 +44288,8 @@ function asyncBuffer(file) {
|
|
|
43433
44288
|
}
|
|
43434
44289
|
function readColumns2(value) {
|
|
43435
44290
|
if (value === void 0 || value === null) return void 0;
|
|
43436
|
-
if (!Array.isArray(value) || value.length === 0 || value.length >
|
|
43437
|
-
throw new Error(`columns must contain 1 through ${
|
|
44291
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > MAX_COLUMNS4) {
|
|
44292
|
+
throw new Error(`columns must contain 1 through ${MAX_COLUMNS4} names`);
|
|
43438
44293
|
}
|
|
43439
44294
|
const columns = value.map((entry, index) => {
|
|
43440
44295
|
if (typeof entry !== "string" || entry.length === 0) throw new Error(`columns[${index}] must be a non-empty string`);
|
|
@@ -43890,6 +44745,759 @@ var quickbooksConnector = declarativeRestConnector({
|
|
|
43890
44745
|
]
|
|
43891
44746
|
});
|
|
43892
44747
|
|
|
44748
|
+
// src/connectors/adapters/rabbitmq.ts
|
|
44749
|
+
import { createHash as createHash7 } from "crypto";
|
|
44750
|
+
import { isIP as isIP3 } from "net";
|
|
44751
|
+
import {
|
|
44752
|
+
connect as amqpConnect
|
|
44753
|
+
} from "amqplib";
|
|
44754
|
+
var MAX_MESSAGE_BYTES3 = 1024 * 1024;
|
|
44755
|
+
var MAX_NAME_BYTES = 255;
|
|
44756
|
+
var MAX_PEM_BYTES = 64 * 1024;
|
|
44757
|
+
var MAX_CA_CERTIFICATES = 16;
|
|
44758
|
+
var MAX_CA_BYTES = 256 * 1024;
|
|
44759
|
+
function createRabbitMqConnector(options = {}) {
|
|
44760
|
+
const connect = options.connect ?? ((connectOptions, socketOptions) => amqpConnect(connectOptions, socketOptions));
|
|
44761
|
+
const resolveHost = options.resolveHost ?? resolvePublicHostAddresses;
|
|
44762
|
+
return {
|
|
44763
|
+
manifest: {
|
|
44764
|
+
kind: "rabbitmq",
|
|
44765
|
+
displayName: "RabbitMQ",
|
|
44766
|
+
description: "Inspect queues and exchanges and publish confirmed JSON messages over verified TLS.",
|
|
44767
|
+
auth: {
|
|
44768
|
+
kind: "api-key",
|
|
44769
|
+
hint: "JSON with a public RabbitMQ host, username, password, virtual host, and optional TLS client credentials."
|
|
44770
|
+
},
|
|
44771
|
+
defaultConsistencyModel: "advisory",
|
|
44772
|
+
category: "webhook",
|
|
44773
|
+
rateLimit: { requests: 120, windowMs: 6e4, scope: "data-source" },
|
|
44774
|
+
capabilities: [
|
|
44775
|
+
readCapability6("rabbitmq.queues.inspect", "Check a queue and return its current message and consumer counts.", {
|
|
44776
|
+
type: "object",
|
|
44777
|
+
properties: { queue: nameSchema("queue") },
|
|
44778
|
+
required: ["queue"],
|
|
44779
|
+
additionalProperties: false
|
|
44780
|
+
}),
|
|
44781
|
+
readCapability6("rabbitmq.exchanges.inspect", "Check whether a named exchange exists and is visible to the connection.", {
|
|
44782
|
+
type: "object",
|
|
44783
|
+
properties: { exchange: nameSchema("exchange") },
|
|
44784
|
+
required: ["exchange"],
|
|
44785
|
+
additionalProperties: false
|
|
44786
|
+
}),
|
|
44787
|
+
mutationCapability6("send.message.to.queue", "Publish a persistent JSON message directly to an existing queue with broker confirmation.", {
|
|
44788
|
+
type: "object",
|
|
44789
|
+
properties: {
|
|
44790
|
+
queue: nameSchema("queue"),
|
|
44791
|
+
data: {},
|
|
44792
|
+
persistent: { type: "boolean", default: true },
|
|
44793
|
+
expirationMs: { type: "integer", minimum: 1, maximum: 6048e5 },
|
|
44794
|
+
priority: { type: "integer", minimum: 0, maximum: 255 }
|
|
44795
|
+
},
|
|
44796
|
+
required: ["queue", "data"],
|
|
44797
|
+
additionalProperties: false
|
|
44798
|
+
}),
|
|
44799
|
+
mutationCapability6("send.message.to.exchange", "Publish a persistent JSON message to an existing exchange with broker confirmation.", {
|
|
44800
|
+
type: "object",
|
|
44801
|
+
properties: {
|
|
44802
|
+
exchange: nameSchema("exchange"),
|
|
44803
|
+
routingKey: { type: "string", maxLength: MAX_NAME_BYTES, default: "" },
|
|
44804
|
+
data: {},
|
|
44805
|
+
persistent: { type: "boolean", default: true },
|
|
44806
|
+
expirationMs: { type: "integer", minimum: 1, maximum: 6048e5 },
|
|
44807
|
+
priority: { type: "integer", minimum: 0, maximum: 255 }
|
|
44808
|
+
},
|
|
44809
|
+
required: ["exchange", "data"],
|
|
44810
|
+
additionalProperties: false
|
|
44811
|
+
})
|
|
44812
|
+
]
|
|
44813
|
+
},
|
|
44814
|
+
async executeRead({ source, capabilityName, args }) {
|
|
44815
|
+
if (capabilityName === "rabbitmq.queues.inspect") {
|
|
44816
|
+
const queue = readName(args.queue, "queue");
|
|
44817
|
+
const data4 = await withChannel(source, connect, resolveHost, async (channel) => {
|
|
44818
|
+
const details = await channel.checkQueue(queue);
|
|
44819
|
+
return { queue: details.queue, messageCount: details.messageCount, consumerCount: details.consumerCount };
|
|
44820
|
+
});
|
|
44821
|
+
return { data: data4, fetchedAt: Date.now() };
|
|
44822
|
+
}
|
|
44823
|
+
if (capabilityName === "rabbitmq.exchanges.inspect") {
|
|
44824
|
+
const exchange = readName(args.exchange, "exchange");
|
|
44825
|
+
const data4 = await withChannel(source, connect, resolveHost, async (channel) => {
|
|
44826
|
+
await channel.checkExchange(exchange);
|
|
44827
|
+
return { exchange, exists: true };
|
|
44828
|
+
});
|
|
44829
|
+
return { data: data4, fetchedAt: Date.now() };
|
|
44830
|
+
}
|
|
44831
|
+
throw new Error(`Unknown RabbitMQ read capability: ${capabilityName}`);
|
|
44832
|
+
},
|
|
44833
|
+
async executeMutation({ source, capabilityName, args, idempotencyKey }) {
|
|
44834
|
+
let data4;
|
|
44835
|
+
if (capabilityName === "send.message.to.queue") {
|
|
44836
|
+
const queue = readName(args.queue, "queue");
|
|
44837
|
+
const body = readJsonMessage(args.data);
|
|
44838
|
+
const publishOptions = readPublishOptions(args, idempotencyKey);
|
|
44839
|
+
data4 = await withConfirmChannel(source, connect, resolveHost, async (channel) => {
|
|
44840
|
+
await channel.checkQueue(queue);
|
|
44841
|
+
await publishConfirmed(channel, publishOptions, (options2) => channel.sendToQueue(queue, body, options2));
|
|
44842
|
+
return { queue, confirmed: true, messageId: publishOptions.messageId, byteLength: body.byteLength };
|
|
44843
|
+
});
|
|
44844
|
+
} else if (capabilityName === "send.message.to.exchange") {
|
|
44845
|
+
const exchange = readName(args.exchange, "exchange");
|
|
44846
|
+
const routingKey = readRoutingKey(args.routingKey);
|
|
44847
|
+
const body = readJsonMessage(args.data);
|
|
44848
|
+
const publishOptions = readPublishOptions(args, idempotencyKey);
|
|
44849
|
+
data4 = await withConfirmChannel(source, connect, resolveHost, async (channel) => {
|
|
44850
|
+
await channel.checkExchange(exchange);
|
|
44851
|
+
await publishConfirmed(channel, publishOptions, (options2) => channel.publish(exchange, routingKey, body, options2));
|
|
44852
|
+
return { exchange, routingKey, confirmed: true, messageId: publishOptions.messageId, byteLength: body.byteLength };
|
|
44853
|
+
});
|
|
44854
|
+
} else {
|
|
44855
|
+
throw new Error(`Unknown RabbitMQ mutation capability: ${capabilityName}`);
|
|
44856
|
+
}
|
|
44857
|
+
return { status: "committed", data: data4, committedAt: Date.now(), idempotentReplay: false };
|
|
44858
|
+
},
|
|
44859
|
+
async test(source) {
|
|
44860
|
+
try {
|
|
44861
|
+
const credentials = readCredentials2(source);
|
|
44862
|
+
await resolveHost(credentials.host);
|
|
44863
|
+
const model2 = await connect(credentials.connectOptions, credentials.socketOptions);
|
|
44864
|
+
await model2.close();
|
|
44865
|
+
return { ok: true };
|
|
44866
|
+
} catch (error) {
|
|
44867
|
+
return { ok: false, reason: safeErrorMessage(error, credentialSecrets(source)) };
|
|
44868
|
+
}
|
|
44869
|
+
}
|
|
44870
|
+
};
|
|
44871
|
+
}
|
|
44872
|
+
var rabbitMqConnector = createRabbitMqConnector();
|
|
44873
|
+
async function withChannel(source, connect, resolveHost, run) {
|
|
44874
|
+
const credentials = readCredentials2(source);
|
|
44875
|
+
await resolveHost(credentials.host);
|
|
44876
|
+
let model2;
|
|
44877
|
+
let channel;
|
|
44878
|
+
try {
|
|
44879
|
+
model2 = await connect(credentials.connectOptions, credentials.socketOptions);
|
|
44880
|
+
channel = await model2.createChannel();
|
|
44881
|
+
return await run(channel);
|
|
44882
|
+
} catch (error) {
|
|
44883
|
+
throw new Error(safeErrorMessage(error, credentials.secrets));
|
|
44884
|
+
} finally {
|
|
44885
|
+
await channel?.close().catch(() => void 0);
|
|
44886
|
+
await model2?.close().catch(() => void 0);
|
|
44887
|
+
}
|
|
44888
|
+
}
|
|
44889
|
+
async function withConfirmChannel(source, connect, resolveHost, run) {
|
|
44890
|
+
const credentials = readCredentials2(source);
|
|
44891
|
+
await resolveHost(credentials.host);
|
|
44892
|
+
let model2;
|
|
44893
|
+
let channel;
|
|
44894
|
+
try {
|
|
44895
|
+
model2 = await connect(credentials.connectOptions, credentials.socketOptions);
|
|
44896
|
+
channel = await model2.createConfirmChannel();
|
|
44897
|
+
return await run(channel);
|
|
44898
|
+
} catch (error) {
|
|
44899
|
+
throw new Error(safeErrorMessage(error, credentials.secrets));
|
|
44900
|
+
} finally {
|
|
44901
|
+
await channel?.close().catch(() => void 0);
|
|
44902
|
+
await model2?.close().catch(() => void 0);
|
|
44903
|
+
}
|
|
44904
|
+
}
|
|
44905
|
+
async function publishConfirmed(channel, publishOptions, publish) {
|
|
44906
|
+
let returned;
|
|
44907
|
+
const onReturn = (message) => {
|
|
44908
|
+
returned = message;
|
|
44909
|
+
};
|
|
44910
|
+
channel.on("return", onReturn);
|
|
44911
|
+
try {
|
|
44912
|
+
publish(publishOptions);
|
|
44913
|
+
await channel.waitForConfirms();
|
|
44914
|
+
if (returned) throw new Error("RabbitMQ accepted but could not route the message");
|
|
44915
|
+
} finally {
|
|
44916
|
+
channel.removeListener("return", onReturn);
|
|
44917
|
+
}
|
|
44918
|
+
}
|
|
44919
|
+
function readCredentials2(source) {
|
|
44920
|
+
let raw;
|
|
44921
|
+
if (source.credentials.kind === "custom") raw = source.credentials.values;
|
|
44922
|
+
else if (source.credentials.kind === "api-key") {
|
|
44923
|
+
try {
|
|
44924
|
+
raw = JSON.parse(source.credentials.apiKey);
|
|
44925
|
+
} catch {
|
|
44926
|
+
throw new Error("RabbitMQ credential must be valid JSON");
|
|
44927
|
+
}
|
|
44928
|
+
} else {
|
|
44929
|
+
throw new Error("RabbitMQ requires a structured credential bundle");
|
|
44930
|
+
}
|
|
44931
|
+
if (!isPlainRecord(raw)) throw new Error("RabbitMQ credential must be a JSON object");
|
|
44932
|
+
const host = readHost(raw.host);
|
|
44933
|
+
const port = readBoundedInteger(raw.port, 5671, 1, 65535, "port");
|
|
44934
|
+
const username = readRequiredString(raw.username, "username", 1024);
|
|
44935
|
+
const password = readRequiredString(raw.password, "password", 4096);
|
|
44936
|
+
const vhost = readOptionalString(raw.vhost, "vhost") ?? "/";
|
|
44937
|
+
if (Buffer.byteLength(vhost, "utf8") > MAX_NAME_BYTES || /[\u0000-\u001f\u007f]/.test(vhost)) {
|
|
44938
|
+
throw new Error(`vhost must be at most ${MAX_NAME_BYTES} bytes without control characters`);
|
|
44939
|
+
}
|
|
44940
|
+
const ca = readPem(raw.tlsCa, "tlsCa", true);
|
|
44941
|
+
const cert = readPem(raw.tlsCert, "tlsCert", false);
|
|
44942
|
+
const key = readPem(raw.tlsKey, "tlsKey", false);
|
|
44943
|
+
if (Boolean(cert) !== Boolean(key)) throw new Error("tlsCert and tlsKey must be supplied together");
|
|
44944
|
+
const passphrase = readOptionalString(raw.tlsPassphrase, "tlsPassphrase");
|
|
44945
|
+
return {
|
|
44946
|
+
host,
|
|
44947
|
+
connectOptions: {
|
|
44948
|
+
protocol: "amqps",
|
|
44949
|
+
hostname: host,
|
|
44950
|
+
port,
|
|
44951
|
+
username,
|
|
44952
|
+
password,
|
|
44953
|
+
vhost,
|
|
44954
|
+
heartbeat: readBoundedInteger(raw.heartbeatSeconds, 30, 5, 300, "heartbeatSeconds"),
|
|
44955
|
+
frameMax: MAX_MESSAGE_BYTES3 + 4096
|
|
44956
|
+
},
|
|
44957
|
+
socketOptions: {
|
|
44958
|
+
minVersion: "TLSv1.2",
|
|
44959
|
+
rejectUnauthorized: true,
|
|
44960
|
+
servername: isIP3(host) === 0 ? host : void 0,
|
|
44961
|
+
lookup: publicDnsLookup,
|
|
44962
|
+
timeout: readBoundedInteger(raw.connectionTimeoutMs, 1e4, 1e3, 3e4, "connectionTimeoutMs"),
|
|
44963
|
+
noDelay: true,
|
|
44964
|
+
keepAlive: true,
|
|
44965
|
+
keepAliveDelay: 6e4,
|
|
44966
|
+
ca,
|
|
44967
|
+
cert,
|
|
44968
|
+
key,
|
|
44969
|
+
passphrase
|
|
44970
|
+
},
|
|
44971
|
+
secrets: [password, ...cert ? [cert] : [], ...key ? [key] : [], ...passphrase ? [passphrase] : []]
|
|
44972
|
+
};
|
|
44973
|
+
}
|
|
44974
|
+
function readHost(value) {
|
|
44975
|
+
const host = readRequiredString(value, "host", 253);
|
|
44976
|
+
const ipVersion = isIP3(host);
|
|
44977
|
+
if (ipVersion !== 0) {
|
|
44978
|
+
if (!isPublicNetworkAddress(host)) throw new Error("RabbitMQ host is not a public network target");
|
|
44979
|
+
return host;
|
|
44980
|
+
}
|
|
44981
|
+
const normalized = host.toLowerCase();
|
|
44982
|
+
if (normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".local") || /[\s/@:\[\]]/.test(host)) {
|
|
44983
|
+
throw new Error("RabbitMQ host must be a public hostname or IP address without a scheme or port");
|
|
44984
|
+
}
|
|
44985
|
+
return host;
|
|
44986
|
+
}
|
|
44987
|
+
function readPublishOptions(args, idempotencyKey) {
|
|
44988
|
+
const expirationMs = args.expirationMs === void 0 ? void 0 : readBoundedInteger(args.expirationMs, 0, 1, 6048e5, "expirationMs");
|
|
44989
|
+
const priority = args.priority === void 0 ? void 0 : readBoundedInteger(args.priority, 0, 0, 255, "priority");
|
|
44990
|
+
return {
|
|
44991
|
+
mandatory: true,
|
|
44992
|
+
persistent: readBoolean(args.persistent, true, "persistent"),
|
|
44993
|
+
contentType: "application/json",
|
|
44994
|
+
contentEncoding: "utf-8",
|
|
44995
|
+
messageId: createHash7("sha256").update(idempotencyKey).digest("hex"),
|
|
44996
|
+
timestamp: Math.floor(Date.now() / 1e3),
|
|
44997
|
+
appId: "tangle-integration-hub",
|
|
44998
|
+
expiration: expirationMs === void 0 ? void 0 : String(expirationMs),
|
|
44999
|
+
priority
|
|
45000
|
+
};
|
|
45001
|
+
}
|
|
45002
|
+
function readJsonMessage(value) {
|
|
45003
|
+
if (value === void 0) throw new Error("data is required");
|
|
45004
|
+
let serialized;
|
|
45005
|
+
try {
|
|
45006
|
+
serialized = JSON.stringify(value);
|
|
45007
|
+
} catch {
|
|
45008
|
+
throw new Error("data must be JSON-serializable");
|
|
45009
|
+
}
|
|
45010
|
+
if (serialized === void 0) throw new Error("data must be JSON-serializable");
|
|
45011
|
+
const body = Buffer.from(serialized);
|
|
45012
|
+
if (body.byteLength > MAX_MESSAGE_BYTES3) throw new Error(`data exceeds the ${MAX_MESSAGE_BYTES3}-byte limit`);
|
|
45013
|
+
return body;
|
|
45014
|
+
}
|
|
45015
|
+
function readName(value, label) {
|
|
45016
|
+
const name = readRequiredString(value, label, MAX_NAME_BYTES);
|
|
45017
|
+
if (Buffer.byteLength(name, "utf8") > MAX_NAME_BYTES || /[\u0000-\u001f\u007f]/.test(name)) {
|
|
45018
|
+
throw new Error(`${label} must be at most ${MAX_NAME_BYTES} bytes without control characters`);
|
|
45019
|
+
}
|
|
45020
|
+
return name;
|
|
45021
|
+
}
|
|
45022
|
+
function readRoutingKey(value) {
|
|
45023
|
+
if (value === void 0 || value === null || value === "") return "";
|
|
45024
|
+
return readName(value, "routingKey");
|
|
45025
|
+
}
|
|
45026
|
+
function readRequiredString(value, label, maxLength) {
|
|
45027
|
+
if (typeof value !== "string" || value.length === 0 || value.length > maxLength) {
|
|
45028
|
+
throw new Error(`${label} must be a non-empty string no longer than ${maxLength} characters`);
|
|
45029
|
+
}
|
|
45030
|
+
return value;
|
|
45031
|
+
}
|
|
45032
|
+
function readPem(value, label, allowArray) {
|
|
45033
|
+
if (value === void 0 || value === null) return void 0;
|
|
45034
|
+
const values = Array.isArray(value) && allowArray ? value : [value];
|
|
45035
|
+
if (values.length === 0 || values.length > MAX_CA_CERTIFICATES || values.some((entry) => typeof entry !== "string" || entry.length === 0 || Buffer.byteLength(entry, "utf8") > MAX_PEM_BYTES) || values.reduce((total, entry) => total + (typeof entry === "string" ? Buffer.byteLength(entry, "utf8") : 0), 0) > MAX_CA_BYTES) {
|
|
45036
|
+
throw new Error(
|
|
45037
|
+
allowArray ? `${label} must contain 1-${MAX_CA_CERTIFICATES} PEM strings, each under ${MAX_PEM_BYTES} bytes and ${MAX_CA_BYTES} bytes total` : `${label} must be a PEM string under ${MAX_PEM_BYTES} bytes`
|
|
45038
|
+
);
|
|
45039
|
+
}
|
|
45040
|
+
return allowArray ? values : values[0];
|
|
45041
|
+
}
|
|
45042
|
+
function credentialSecrets(source) {
|
|
45043
|
+
try {
|
|
45044
|
+
return readCredentials2(source).secrets;
|
|
45045
|
+
} catch {
|
|
45046
|
+
return [];
|
|
45047
|
+
}
|
|
45048
|
+
}
|
|
45049
|
+
function safeErrorMessage(error, secrets) {
|
|
45050
|
+
let message = error instanceof Error ? error.message : String(error);
|
|
45051
|
+
for (const secret of secrets) {
|
|
45052
|
+
if (secret.length >= 4) message = message.replaceAll(secret, "[REDACTED]");
|
|
45053
|
+
}
|
|
45054
|
+
return message;
|
|
45055
|
+
}
|
|
45056
|
+
function readCapability6(name, description, parameters) {
|
|
45057
|
+
return { name, class: "read", description, parameters };
|
|
45058
|
+
}
|
|
45059
|
+
function mutationCapability6(name, description, parameters) {
|
|
45060
|
+
return { name, class: "mutation", description, parameters, cas: "none", externalEffect: true };
|
|
45061
|
+
}
|
|
45062
|
+
function nameSchema(label) {
|
|
45063
|
+
return { type: "string", minLength: 1, maxLength: MAX_NAME_BYTES, description: `Existing RabbitMQ ${label} name.` };
|
|
45064
|
+
}
|
|
45065
|
+
|
|
45066
|
+
// src/connectors/adapters/redis.ts
|
|
45067
|
+
import { isIP as isIP4 } from "net";
|
|
45068
|
+
import {
|
|
45069
|
+
createClient as createRedisClient
|
|
45070
|
+
} from "redis";
|
|
45071
|
+
var MAX_KEY_BYTES = 1024;
|
|
45072
|
+
var MAX_PATTERN_BYTES = 4096;
|
|
45073
|
+
var MAX_VALUE_BYTES = 1024 * 1024;
|
|
45074
|
+
var MAX_SCAN_KEYS = 1e3;
|
|
45075
|
+
var MAX_SCAN_BYTES = 1024 * 1024;
|
|
45076
|
+
var MAX_TLS_CA_BYTES = 256 * 1024;
|
|
45077
|
+
var MAX_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
45078
|
+
var SCAN_SCRIPT = `
|
|
45079
|
+
local page
|
|
45080
|
+
if ARGV[4] == '' then
|
|
45081
|
+
page = redis.call('SCAN', ARGV[1], 'MATCH', ARGV[2], 'COUNT', ARGV[3])
|
|
45082
|
+
else
|
|
45083
|
+
page = redis.call('SCAN', ARGV[1], 'MATCH', ARGV[2], 'COUNT', ARGV[3], 'TYPE', ARGV[4])
|
|
45084
|
+
end
|
|
45085
|
+
local keys = page[2]
|
|
45086
|
+
if #keys > tonumber(ARGV[5]) then return redis.error_reply('Redis SCAN page exceeds the configured key limit') end
|
|
45087
|
+
local bytes = 0
|
|
45088
|
+
for _, key in ipairs(keys) do
|
|
45089
|
+
if string.len(key) > tonumber(ARGV[7]) then return redis.error_reply('Redis SCAN key exceeds the configured key limit') end
|
|
45090
|
+
bytes = bytes + string.len(key)
|
|
45091
|
+
if bytes > tonumber(ARGV[6]) then return redis.error_reply('Redis SCAN page exceeds the configured byte limit') end
|
|
45092
|
+
end
|
|
45093
|
+
return page
|
|
45094
|
+
`;
|
|
45095
|
+
var INSPECT_SCRIPT = `
|
|
45096
|
+
local kind = redis.call('TYPE', KEYS[1])
|
|
45097
|
+
if type(kind) == 'table' then kind = kind['ok'] end
|
|
45098
|
+
return { kind, redis.call('PTTL', KEYS[1]) }
|
|
45099
|
+
`;
|
|
45100
|
+
var READ_STRING_SCRIPT = `
|
|
45101
|
+
local kind = redis.call('TYPE', KEYS[1])
|
|
45102
|
+
if type(kind) == 'table' then kind = kind['ok'] end
|
|
45103
|
+
local ttl = redis.call('PTTL', KEYS[1])
|
|
45104
|
+
if kind == 'none' then return { kind, ttl, false, 0 } end
|
|
45105
|
+
if kind ~= 'string' then return redis.error_reply('WRONGTYPE key does not contain a string') end
|
|
45106
|
+
local length = redis.call('STRLEN', KEYS[1])
|
|
45107
|
+
if length > tonumber(ARGV[1]) then return redis.error_reply('Redis value exceeds the configured read limit') end
|
|
45108
|
+
return { kind, ttl, redis.call('GET', KEYS[1]), length }
|
|
45109
|
+
`;
|
|
45110
|
+
var COMPARE_SET_SCRIPT = `
|
|
45111
|
+
if redis.call('GET', KEYS[1]) ~= ARGV[1] then return 0 end
|
|
45112
|
+
if ARGV[3] ~= '' then
|
|
45113
|
+
redis.call('SET', KEYS[1], ARGV[2], 'PX', ARGV[3])
|
|
45114
|
+
elseif ARGV[4] == '1' then
|
|
45115
|
+
redis.call('SET', KEYS[1], ARGV[2], 'KEEPTTL')
|
|
45116
|
+
else
|
|
45117
|
+
redis.call('SET', KEYS[1], ARGV[2])
|
|
45118
|
+
end
|
|
45119
|
+
return 1
|
|
45120
|
+
`;
|
|
45121
|
+
var COMPARE_DELETE_SCRIPT = `
|
|
45122
|
+
if redis.call('GET', KEYS[1]) ~= ARGV[1] then return 0 end
|
|
45123
|
+
return redis.call('DEL', KEYS[1])
|
|
45124
|
+
`;
|
|
45125
|
+
function createRedisConnector(options = {}) {
|
|
45126
|
+
const createClient = options.createClient ?? defaultCreateClient;
|
|
45127
|
+
const resolveHost = options.resolveHost ?? resolvePublicHostAddresses;
|
|
45128
|
+
return {
|
|
45129
|
+
manifest: {
|
|
45130
|
+
kind: "redis",
|
|
45131
|
+
displayName: "Redis",
|
|
45132
|
+
description: "Inspect and scan Redis keys and perform bounded, approved string compare-and-swap mutations over verified TLS.",
|
|
45133
|
+
auth: {
|
|
45134
|
+
kind: "api-key",
|
|
45135
|
+
hint: "JSON with a public Redis host, password, optional ACL username/database, and optional TLS CA. Verified TLS is mandatory."
|
|
45136
|
+
},
|
|
45137
|
+
defaultConsistencyModel: "authoritative",
|
|
45138
|
+
category: "database",
|
|
45139
|
+
rateLimit: { requests: 120, windowMs: 6e4, scope: "data-source" },
|
|
45140
|
+
capabilities: [
|
|
45141
|
+
readCapability7("redis.keys.scan", "Read one bounded SCAN page without blocking the server with KEYS.", {
|
|
45142
|
+
type: "object",
|
|
45143
|
+
properties: {
|
|
45144
|
+
cursor: { type: "string", pattern: "^[0-9]+$", default: "0" },
|
|
45145
|
+
match: { type: "string", maxLength: MAX_PATTERN_BYTES, default: "*" },
|
|
45146
|
+
count: { type: "integer", minimum: 1, maximum: MAX_SCAN_KEYS, default: 100 },
|
|
45147
|
+
type: { type: "string", enum: ["string", "list", "set", "zset", "hash", "stream"] }
|
|
45148
|
+
},
|
|
45149
|
+
additionalProperties: false
|
|
45150
|
+
}),
|
|
45151
|
+
readCapability7("redis.key.inspect", "Read a key type and remaining expiry atomically.", keySchema()),
|
|
45152
|
+
readCapability7("redis.string.get", "Read one bounded string value, type, and remaining expiry atomically.", keySchema()),
|
|
45153
|
+
mutationCapability7("redis.string.set", "Set a bounded string only when the key is absent, present, or matches an expected value.", {
|
|
45154
|
+
type: "object",
|
|
45155
|
+
properties: {
|
|
45156
|
+
key: keyParameter(),
|
|
45157
|
+
value: { type: "string", maxLength: MAX_VALUE_BYTES },
|
|
45158
|
+
condition: { type: "string", enum: ["absent", "present", "matches"] },
|
|
45159
|
+
expectedValue: { type: "string", maxLength: MAX_VALUE_BYTES },
|
|
45160
|
+
ttlMs: { type: "integer", minimum: 1, maximum: MAX_TTL_MS },
|
|
45161
|
+
keepTtl: { type: "boolean", default: false }
|
|
45162
|
+
},
|
|
45163
|
+
required: ["key", "value", "condition"],
|
|
45164
|
+
additionalProperties: false
|
|
45165
|
+
}),
|
|
45166
|
+
mutationCapability7("redis.string.delete", "Delete a string key only when its current value exactly matches the caller expectation.", {
|
|
45167
|
+
type: "object",
|
|
45168
|
+
properties: {
|
|
45169
|
+
key: keyParameter(),
|
|
45170
|
+
expectedValue: { type: "string", maxLength: MAX_VALUE_BYTES }
|
|
45171
|
+
},
|
|
45172
|
+
required: ["key", "expectedValue"],
|
|
45173
|
+
additionalProperties: false
|
|
45174
|
+
})
|
|
45175
|
+
]
|
|
45176
|
+
},
|
|
45177
|
+
async test(source) {
|
|
45178
|
+
try {
|
|
45179
|
+
await withClient(source, createClient, resolveHost, async (client) => {
|
|
45180
|
+
const response = await client.ping();
|
|
45181
|
+
if (response !== "PONG") throw new Error("Redis PING returned an unexpected response");
|
|
45182
|
+
});
|
|
45183
|
+
return { ok: true };
|
|
45184
|
+
} catch (error) {
|
|
45185
|
+
return { ok: false, reason: safeErrorMessage2(error, credentialSecrets2(source)) };
|
|
45186
|
+
}
|
|
45187
|
+
},
|
|
45188
|
+
async executeRead({ source, capabilityName, args }) {
|
|
45189
|
+
if (capabilityName === "redis.keys.scan") {
|
|
45190
|
+
const cursor = readCursor(args.cursor);
|
|
45191
|
+
const match = readPattern(args.match);
|
|
45192
|
+
const count = readBoundedInteger(args.count, 100, 1, MAX_SCAN_KEYS, "count");
|
|
45193
|
+
const type = readScanType(args.type);
|
|
45194
|
+
const data4 = await withClient(source, createClient, resolveHost, async (client) => {
|
|
45195
|
+
const reply = readArrayReply(await client.eval(SCAN_SCRIPT, {
|
|
45196
|
+
keys: [],
|
|
45197
|
+
arguments: [
|
|
45198
|
+
cursor,
|
|
45199
|
+
match,
|
|
45200
|
+
String(count),
|
|
45201
|
+
type ?? "",
|
|
45202
|
+
String(MAX_SCAN_KEYS),
|
|
45203
|
+
String(MAX_SCAN_BYTES),
|
|
45204
|
+
String(MAX_KEY_BYTES)
|
|
45205
|
+
]
|
|
45206
|
+
}), "scan");
|
|
45207
|
+
const keys = readStringArray(reply[1], "scan keys");
|
|
45208
|
+
assertScanPage(keys);
|
|
45209
|
+
return { cursor: readCursor(readReplyString(reply[0], "cursor")), keys };
|
|
45210
|
+
});
|
|
45211
|
+
return { data: data4, fetchedAt: Date.now() };
|
|
45212
|
+
}
|
|
45213
|
+
if (capabilityName === "redis.key.inspect") {
|
|
45214
|
+
const key = readKey(args.key);
|
|
45215
|
+
const data4 = await withClient(source, createClient, resolveHost, async (client) => {
|
|
45216
|
+
const reply = readArrayReply(await client.eval(INSPECT_SCRIPT, { keys: [key], arguments: [] }), "inspect");
|
|
45217
|
+
const type = readReplyString(reply[0], "type");
|
|
45218
|
+
return { key, exists: type !== "none", type, ttlMs: normalizeTtl(reply[1]) };
|
|
45219
|
+
});
|
|
45220
|
+
return { data: data4, fetchedAt: Date.now() };
|
|
45221
|
+
}
|
|
45222
|
+
if (capabilityName === "redis.string.get") {
|
|
45223
|
+
const key = readKey(args.key);
|
|
45224
|
+
const data4 = await withClient(source, createClient, resolveHost, async (client) => {
|
|
45225
|
+
const reply = readArrayReply(await client.eval(READ_STRING_SCRIPT, {
|
|
45226
|
+
keys: [key],
|
|
45227
|
+
arguments: [String(MAX_VALUE_BYTES)]
|
|
45228
|
+
}), "string read");
|
|
45229
|
+
const type = readReplyString(reply[0], "type");
|
|
45230
|
+
const exists = type !== "none";
|
|
45231
|
+
const value = exists ? readReplyString(reply[2], "value") : null;
|
|
45232
|
+
if (value !== null && Buffer.byteLength(value, "utf8") > MAX_VALUE_BYTES) {
|
|
45233
|
+
throw new Error(`Redis value exceeds the ${MAX_VALUE_BYTES}-byte limit`);
|
|
45234
|
+
}
|
|
45235
|
+
return {
|
|
45236
|
+
key,
|
|
45237
|
+
exists,
|
|
45238
|
+
type,
|
|
45239
|
+
ttlMs: normalizeTtl(reply[1]),
|
|
45240
|
+
value,
|
|
45241
|
+
byteLength: exists ? readReplyInteger(reply[3], "byteLength") : 0
|
|
45242
|
+
};
|
|
45243
|
+
});
|
|
45244
|
+
return { data: data4, fetchedAt: Date.now() };
|
|
45245
|
+
}
|
|
45246
|
+
throw new Error(`Unknown Redis read capability: ${capabilityName}`);
|
|
45247
|
+
},
|
|
45248
|
+
async executeMutation({ source, capabilityName, args }) {
|
|
45249
|
+
if (capabilityName === "redis.string.set") {
|
|
45250
|
+
const input = readSetInput(args);
|
|
45251
|
+
return withClient(source, createClient, resolveHost, async (client) => {
|
|
45252
|
+
let committed;
|
|
45253
|
+
if (input.condition === "matches") {
|
|
45254
|
+
committed = readReplyInteger(await client.eval(COMPARE_SET_SCRIPT, {
|
|
45255
|
+
keys: [input.key],
|
|
45256
|
+
arguments: [input.expectedValue, input.value, input.ttlMs === void 0 ? "" : String(input.ttlMs), input.keepTtl ? "1" : "0"]
|
|
45257
|
+
}), "compare-and-set result") === 1;
|
|
45258
|
+
} else {
|
|
45259
|
+
const response = await client.set(input.key, input.value, {
|
|
45260
|
+
condition: input.condition === "absent" ? "NX" : "XX",
|
|
45261
|
+
expiration: input.ttlMs === void 0 ? input.keepTtl ? "KEEPTTL" : void 0 : { type: "PX", value: input.ttlMs }
|
|
45262
|
+
});
|
|
45263
|
+
committed = response === "OK";
|
|
45264
|
+
}
|
|
45265
|
+
if (!committed) return conflict(`Redis key ${input.key} did not satisfy condition ${input.condition}`);
|
|
45266
|
+
return committedResult({ key: input.key, condition: input.condition, byteLength: Buffer.byteLength(input.value, "utf8") });
|
|
45267
|
+
});
|
|
45268
|
+
}
|
|
45269
|
+
if (capabilityName === "redis.string.delete") {
|
|
45270
|
+
const key = readKey(args.key);
|
|
45271
|
+
const expectedValue = readValue(args.expectedValue, "expectedValue");
|
|
45272
|
+
return withClient(source, createClient, resolveHost, async (client) => {
|
|
45273
|
+
const deleted = readReplyInteger(await client.eval(COMPARE_DELETE_SCRIPT, {
|
|
45274
|
+
keys: [key],
|
|
45275
|
+
arguments: [expectedValue]
|
|
45276
|
+
}), "compare-and-delete result");
|
|
45277
|
+
if (deleted !== 1) return conflict(`Redis key ${key} did not match the expected value`);
|
|
45278
|
+
return committedResult({ key, deleted: true });
|
|
45279
|
+
});
|
|
45280
|
+
}
|
|
45281
|
+
throw new Error(`Unknown Redis mutation capability: ${capabilityName}`);
|
|
45282
|
+
}
|
|
45283
|
+
};
|
|
45284
|
+
}
|
|
45285
|
+
var redisConnector = createRedisConnector();
|
|
45286
|
+
function defaultCreateClient(options) {
|
|
45287
|
+
return createRedisClient(options);
|
|
45288
|
+
}
|
|
45289
|
+
async function withClient(source, createClient, resolveHost, run) {
|
|
45290
|
+
const credentials = readCredentials3(source);
|
|
45291
|
+
const addresses = await resolveHost(credentials.host);
|
|
45292
|
+
if (addresses.length === 0) throw new Error("Redis host did not resolve");
|
|
45293
|
+
let client;
|
|
45294
|
+
try {
|
|
45295
|
+
client = createClient({
|
|
45296
|
+
username: credentials.username,
|
|
45297
|
+
password: credentials.password,
|
|
45298
|
+
database: credentials.database,
|
|
45299
|
+
name: "tangle-integration-hub",
|
|
45300
|
+
disableOfflineQueue: true,
|
|
45301
|
+
commandsQueueMaxLength: 10,
|
|
45302
|
+
maintNotifications: "disabled",
|
|
45303
|
+
commandOptions: { abortSignal: AbortSignal.timeout(credentials.commandTimeoutMs) },
|
|
45304
|
+
socket: {
|
|
45305
|
+
host: addresses[0],
|
|
45306
|
+
port: credentials.port,
|
|
45307
|
+
tls: true,
|
|
45308
|
+
servername: isIP4(credentials.host) === 0 ? credentials.host : void 0,
|
|
45309
|
+
minVersion: "TLSv1.2",
|
|
45310
|
+
rejectUnauthorized: true,
|
|
45311
|
+
ca: credentials.tlsCa,
|
|
45312
|
+
connectTimeout: credentials.connectTimeoutMs,
|
|
45313
|
+
socketTimeout: credentials.commandTimeoutMs,
|
|
45314
|
+
reconnectStrategy: false
|
|
45315
|
+
}
|
|
45316
|
+
});
|
|
45317
|
+
client.on("error", () => void 0);
|
|
45318
|
+
await client.connect();
|
|
45319
|
+
return await run(client);
|
|
45320
|
+
} catch (error) {
|
|
45321
|
+
throw new Error(safeErrorMessage2(error, credentials.secrets));
|
|
45322
|
+
} finally {
|
|
45323
|
+
if (client?.isOpen) await client.close().catch(() => client?.destroy());
|
|
45324
|
+
else client?.destroy();
|
|
45325
|
+
}
|
|
45326
|
+
}
|
|
45327
|
+
function readCredentials3(source) {
|
|
45328
|
+
let raw;
|
|
45329
|
+
if (source.credentials.kind === "custom") raw = source.credentials.values;
|
|
45330
|
+
else if (source.credentials.kind === "api-key") {
|
|
45331
|
+
try {
|
|
45332
|
+
raw = JSON.parse(source.credentials.apiKey);
|
|
45333
|
+
} catch {
|
|
45334
|
+
throw new Error("Redis credential must be valid JSON");
|
|
45335
|
+
}
|
|
45336
|
+
} else {
|
|
45337
|
+
throw new Error("Redis requires a structured credential bundle");
|
|
45338
|
+
}
|
|
45339
|
+
if (!isPlainRecord(raw)) throw new Error("Redis credential must be a JSON object");
|
|
45340
|
+
const host = readHost2(raw.host);
|
|
45341
|
+
const username = readOptionalString(raw.username, "username");
|
|
45342
|
+
if (username && (username.length > 128 || /[\u0000-\u001f\u007f]/.test(username))) {
|
|
45343
|
+
throw new Error("Redis username must be at most 128 characters without control characters");
|
|
45344
|
+
}
|
|
45345
|
+
const password = readRequiredString2(raw.password, "password", 4096);
|
|
45346
|
+
const tlsCa = readOptionalString(raw.tlsCa, "tlsCa");
|
|
45347
|
+
if (tlsCa && Buffer.byteLength(tlsCa, "utf8") > MAX_TLS_CA_BYTES) {
|
|
45348
|
+
throw new Error(`Redis tlsCa exceeds the ${MAX_TLS_CA_BYTES}-byte limit`);
|
|
45349
|
+
}
|
|
45350
|
+
return {
|
|
45351
|
+
host,
|
|
45352
|
+
port: readBoundedInteger(raw.port, 6380, 1, 65535, "port"),
|
|
45353
|
+
username,
|
|
45354
|
+
password,
|
|
45355
|
+
database: readBoundedInteger(raw.database, 0, 0, 65535, "database"),
|
|
45356
|
+
tlsCa,
|
|
45357
|
+
connectTimeoutMs: readBoundedInteger(raw.connectTimeoutMs, 1e4, 1e3, 3e4, "connectTimeoutMs"),
|
|
45358
|
+
commandTimeoutMs: readBoundedInteger(raw.commandTimeoutMs, 1e4, 1e3, 3e4, "commandTimeoutMs"),
|
|
45359
|
+
secrets: [password]
|
|
45360
|
+
};
|
|
45361
|
+
}
|
|
45362
|
+
function readHost2(value) {
|
|
45363
|
+
const host = readRequiredString2(value, "host", 253);
|
|
45364
|
+
const ipVersion = isIP4(host);
|
|
45365
|
+
if (ipVersion !== 0) {
|
|
45366
|
+
if (!isPublicNetworkAddress(host)) throw new Error("Redis host is not a public network target");
|
|
45367
|
+
return host;
|
|
45368
|
+
}
|
|
45369
|
+
const normalized = host.toLowerCase();
|
|
45370
|
+
if (normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".local") || /[\s/@:\[\]]/.test(host)) {
|
|
45371
|
+
throw new Error("Redis host must be a public hostname or IP address without a scheme or port");
|
|
45372
|
+
}
|
|
45373
|
+
return host;
|
|
45374
|
+
}
|
|
45375
|
+
function readSetInput(args) {
|
|
45376
|
+
const key = readKey(args.key);
|
|
45377
|
+
const value = readValue(args.value, "value");
|
|
45378
|
+
const condition = args.condition;
|
|
45379
|
+
if (!["absent", "present", "matches"].includes(String(condition))) {
|
|
45380
|
+
throw new Error("condition must be absent, present, or matches");
|
|
45381
|
+
}
|
|
45382
|
+
const expectedValue = args.expectedValue === void 0 ? void 0 : readValue(args.expectedValue, "expectedValue");
|
|
45383
|
+
if (condition === "matches" && expectedValue === void 0) throw new Error("expectedValue is required when condition is matches");
|
|
45384
|
+
if (condition !== "matches" && expectedValue !== void 0) throw new Error("expectedValue is only valid when condition is matches");
|
|
45385
|
+
const ttlMs = args.ttlMs === void 0 ? void 0 : readBoundedInteger(args.ttlMs, 0, 1, MAX_TTL_MS, "ttlMs");
|
|
45386
|
+
const keepTtl = readBoolean(args.keepTtl, false, "keepTtl");
|
|
45387
|
+
if (ttlMs !== void 0 && keepTtl) throw new Error("ttlMs and keepTtl cannot be used together");
|
|
45388
|
+
return { key, value, condition, expectedValue, ttlMs, keepTtl };
|
|
45389
|
+
}
|
|
45390
|
+
function readCursor(value) {
|
|
45391
|
+
if (value === void 0 || value === null || value === "") return "0";
|
|
45392
|
+
if (typeof value !== "string" || !/^[0-9]+$/.test(value) || value.length > 32) {
|
|
45393
|
+
throw new Error("cursor must be a Redis numeric cursor string");
|
|
45394
|
+
}
|
|
45395
|
+
return value;
|
|
45396
|
+
}
|
|
45397
|
+
function readPattern(value) {
|
|
45398
|
+
if (value === void 0 || value === null || value === "") return "*";
|
|
45399
|
+
if (typeof value !== "string" || Buffer.byteLength(value, "utf8") > MAX_PATTERN_BYTES || /[\u0000-\u001f\u007f]/.test(value)) {
|
|
45400
|
+
throw new Error(`match must be under ${MAX_PATTERN_BYTES} bytes without control characters`);
|
|
45401
|
+
}
|
|
45402
|
+
return value;
|
|
45403
|
+
}
|
|
45404
|
+
function readScanType(value) {
|
|
45405
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
45406
|
+
if (!["string", "list", "set", "zset", "hash", "stream"].includes(String(value))) {
|
|
45407
|
+
throw new Error("type must be string, list, set, zset, hash, or stream");
|
|
45408
|
+
}
|
|
45409
|
+
return String(value);
|
|
45410
|
+
}
|
|
45411
|
+
function assertScanPage(keys) {
|
|
45412
|
+
if (!Array.isArray(keys) || keys.length > MAX_SCAN_KEYS) {
|
|
45413
|
+
throw new Error(`Redis SCAN page exceeds ${MAX_SCAN_KEYS} keys`);
|
|
45414
|
+
}
|
|
45415
|
+
if (keys.some((key) => typeof key !== "string" || Buffer.byteLength(key, "utf8") > MAX_KEY_BYTES || /[\u0000-\u001f\u007f]/.test(key)) || Buffer.byteLength(JSON.stringify(keys), "utf8") > MAX_SCAN_BYTES) {
|
|
45416
|
+
throw new Error(`Redis SCAN page exceeds the ${MAX_SCAN_BYTES}-byte limit`);
|
|
45417
|
+
}
|
|
45418
|
+
}
|
|
45419
|
+
function readKey(value) {
|
|
45420
|
+
return readBoundedString(value, "key", MAX_KEY_BYTES);
|
|
45421
|
+
}
|
|
45422
|
+
function readValue(value, label) {
|
|
45423
|
+
if (typeof value !== "string") throw new Error(`${label} must be a string`);
|
|
45424
|
+
if (Buffer.byteLength(value, "utf8") > MAX_VALUE_BYTES) {
|
|
45425
|
+
throw new Error(`${label} exceeds the ${MAX_VALUE_BYTES}-byte limit`);
|
|
45426
|
+
}
|
|
45427
|
+
return value;
|
|
45428
|
+
}
|
|
45429
|
+
function readBoundedString(value, label, maxBytes) {
|
|
45430
|
+
if (typeof value !== "string" || value.length === 0 || Buffer.byteLength(value, "utf8") > maxBytes || /[\u0000-\u001f\u007f]/.test(value)) {
|
|
45431
|
+
throw new Error(`${label} must be a non-empty string under ${maxBytes} bytes without control characters`);
|
|
45432
|
+
}
|
|
45433
|
+
return value;
|
|
45434
|
+
}
|
|
45435
|
+
function readRequiredString2(value, label, maxLength) {
|
|
45436
|
+
if (typeof value !== "string" || value.length === 0 || value.length > maxLength) {
|
|
45437
|
+
throw new Error(`${label} must be a non-empty string no longer than ${maxLength} characters`);
|
|
45438
|
+
}
|
|
45439
|
+
return value;
|
|
45440
|
+
}
|
|
45441
|
+
function readArrayReply(value, label) {
|
|
45442
|
+
if (!Array.isArray(value)) throw new Error(`Redis ${label} returned a malformed response`);
|
|
45443
|
+
return value;
|
|
45444
|
+
}
|
|
45445
|
+
function readStringArray(value, label) {
|
|
45446
|
+
if (!Array.isArray(value)) throw new Error(`Redis response ${label} is malformed`);
|
|
45447
|
+
return value.map((entry) => readReplyString(entry, label));
|
|
45448
|
+
}
|
|
45449
|
+
function readReplyString(value, label) {
|
|
45450
|
+
if (typeof value === "string") return value;
|
|
45451
|
+
if (Buffer.isBuffer(value)) return value.toString("utf8");
|
|
45452
|
+
throw new Error(`Redis response ${label} is malformed`);
|
|
45453
|
+
}
|
|
45454
|
+
function readReplyInteger(value, label) {
|
|
45455
|
+
const parsed = typeof value === "bigint" ? Number(value) : typeof value === "string" ? Number(value) : value;
|
|
45456
|
+
if (typeof parsed !== "number" || !Number.isSafeInteger(parsed)) throw new Error(`Redis response ${label} is malformed`);
|
|
45457
|
+
return parsed;
|
|
45458
|
+
}
|
|
45459
|
+
function normalizeTtl(value) {
|
|
45460
|
+
const ttl = readReplyInteger(value, "ttlMs");
|
|
45461
|
+
return ttl >= 0 ? ttl : null;
|
|
45462
|
+
}
|
|
45463
|
+
function safeErrorMessage2(error, secrets) {
|
|
45464
|
+
let message = error instanceof Error ? error.message : String(error);
|
|
45465
|
+
for (const secret of secrets) {
|
|
45466
|
+
if (secret.length >= 4) message = message.replaceAll(secret, "[REDACTED]");
|
|
45467
|
+
}
|
|
45468
|
+
return message;
|
|
45469
|
+
}
|
|
45470
|
+
function credentialSecrets2(source) {
|
|
45471
|
+
try {
|
|
45472
|
+
return readCredentials3(source).secrets;
|
|
45473
|
+
} catch {
|
|
45474
|
+
return [];
|
|
45475
|
+
}
|
|
45476
|
+
}
|
|
45477
|
+
function committedResult(data4) {
|
|
45478
|
+
return { status: "committed", data: data4, committedAt: Date.now(), idempotentReplay: false };
|
|
45479
|
+
}
|
|
45480
|
+
function conflict(message) {
|
|
45481
|
+
return { status: "conflict", alternatives: [], currentState: null, message };
|
|
45482
|
+
}
|
|
45483
|
+
function readCapability7(name, description, parameters) {
|
|
45484
|
+
return { name, class: "read", description, parameters };
|
|
45485
|
+
}
|
|
45486
|
+
function mutationCapability7(name, description, parameters) {
|
|
45487
|
+
return { name, class: "mutation", description, parameters, cas: "optimistic-read-verify", externalEffect: true };
|
|
45488
|
+
}
|
|
45489
|
+
function keyParameter() {
|
|
45490
|
+
return { type: "string", minLength: 1, maxLength: MAX_KEY_BYTES };
|
|
45491
|
+
}
|
|
45492
|
+
function keySchema() {
|
|
45493
|
+
return {
|
|
45494
|
+
type: "object",
|
|
45495
|
+
properties: { key: keyParameter() },
|
|
45496
|
+
required: ["key"],
|
|
45497
|
+
additionalProperties: false
|
|
45498
|
+
};
|
|
45499
|
+
}
|
|
45500
|
+
|
|
43893
45501
|
// src/connectors/adapters/recall-ai.ts
|
|
43894
45502
|
var recallAiRegions = [
|
|
43895
45503
|
"https://us-east-1.recall.ai",
|
|
@@ -44720,16 +46328,14 @@ var salesforceConnector = declarativeRestConnector({
|
|
|
44720
46328
|
});
|
|
44721
46329
|
|
|
44722
46330
|
// src/connectors/adapters/sftp.ts
|
|
44723
|
-
import { lookup } from "dns/promises";
|
|
44724
46331
|
import { timingSafeEqual as timingSafeEqual4 } from "crypto";
|
|
44725
46332
|
import { posix } from "path";
|
|
44726
|
-
import ipaddr from "ipaddr.js";
|
|
44727
46333
|
import SftpClient from "ssh2-sftp-client";
|
|
44728
46334
|
var MAX_LIST_ENTRIES = 1e3;
|
|
44729
46335
|
var MAX_PATH_LENGTH = 1024;
|
|
44730
46336
|
function createSftpConnector(options = {}) {
|
|
44731
46337
|
const createClient = options.createClient ?? (() => new SftpClient());
|
|
44732
|
-
const lookupHost = options.lookupHost ??
|
|
46338
|
+
const lookupHost = options.lookupHost ?? resolvePublicHostAddresses;
|
|
44733
46339
|
return {
|
|
44734
46340
|
manifest: {
|
|
44735
46341
|
kind: "sftp",
|
|
@@ -44828,7 +46434,7 @@ function createSftpConnector(options = {}) {
|
|
|
44828
46434
|
]
|
|
44829
46435
|
},
|
|
44830
46436
|
async executeRead({ source, capabilityName, args }) {
|
|
44831
|
-
const credentials =
|
|
46437
|
+
const credentials = readCredentials4(source);
|
|
44832
46438
|
if (capabilityName === "sftp.list") {
|
|
44833
46439
|
const path = resolveRemotePath(credentials.rootPath, args.path ?? "/");
|
|
44834
46440
|
const limit = readBoundedInteger(args.limit, MAX_LIST_ENTRIES, 1, MAX_LIST_ENTRIES, "limit");
|
|
@@ -44861,7 +46467,7 @@ function createSftpConnector(options = {}) {
|
|
|
44861
46467
|
throw new Error(`Unknown SFTP read capability: ${capabilityName}`);
|
|
44862
46468
|
},
|
|
44863
46469
|
async executeMutation({ source, capabilityName, args }) {
|
|
44864
|
-
const credentials =
|
|
46470
|
+
const credentials = readCredentials4(source);
|
|
44865
46471
|
const prepared = prepareMutation(capabilityName, args, credentials.rootPath);
|
|
44866
46472
|
return withSftp(credentials, createClient, lookupHost, async (client) => {
|
|
44867
46473
|
let data4;
|
|
@@ -44895,7 +46501,7 @@ function createSftpConnector(options = {}) {
|
|
|
44895
46501
|
},
|
|
44896
46502
|
async test(source) {
|
|
44897
46503
|
try {
|
|
44898
|
-
const credentials =
|
|
46504
|
+
const credentials = readCredentials4(source);
|
|
44899
46505
|
await withSftp(credentials, createClient, lookupHost, (client) => client.cwd());
|
|
44900
46506
|
return { ok: true };
|
|
44901
46507
|
} catch (error) {
|
|
@@ -44963,7 +46569,7 @@ async function withSftp(credentials, createClient, lookupHost, run) {
|
|
|
44963
46569
|
await client.end().catch(() => false);
|
|
44964
46570
|
}
|
|
44965
46571
|
}
|
|
44966
|
-
function
|
|
46572
|
+
function readCredentials4(source) {
|
|
44967
46573
|
let raw;
|
|
44968
46574
|
if (source.credentials.kind === "custom") raw = source.credentials.values;
|
|
44969
46575
|
else if (source.credentials.kind === "api-key") {
|
|
@@ -45004,21 +46610,6 @@ function validateHostname(host) {
|
|
|
45004
46610
|
throw new Error("SFTP host must be a public hostname or IP address without a scheme");
|
|
45005
46611
|
}
|
|
45006
46612
|
}
|
|
45007
|
-
async function resolvePublicAddresses(host) {
|
|
45008
|
-
const addresses = ipaddr.isValid(host) ? [host] : (await lookup(host, { all: true, verbatim: true })).map((entry) => entry.address);
|
|
45009
|
-
if (addresses.length === 0 || addresses.some((address) => !isPublicAddress(address))) {
|
|
45010
|
-
throw new Error("SFTP host is not a public network target");
|
|
45011
|
-
}
|
|
45012
|
-
return addresses;
|
|
45013
|
-
}
|
|
45014
|
-
function isPublicAddress(value) {
|
|
45015
|
-
if (!ipaddr.isValid(value)) return false;
|
|
45016
|
-
let address = ipaddr.parse(value);
|
|
45017
|
-
if (address.kind() === "ipv6" && address.isIPv4MappedAddress()) {
|
|
45018
|
-
address = address.toIPv4Address();
|
|
45019
|
-
}
|
|
45020
|
-
return address.range() === "unicast";
|
|
45021
|
-
}
|
|
45022
46613
|
function parseFingerprint(value) {
|
|
45023
46614
|
if (/^[a-fA-F0-9]{64}$/.test(value)) return Buffer.from(value, "hex");
|
|
45024
46615
|
const match = /^SHA256:([A-Za-z0-9+/]{43}=?)$/.exec(value);
|
|
@@ -54681,17 +56272,17 @@ var zohoBookingsConnector = {
|
|
|
54681
56272
|
category: "calendar",
|
|
54682
56273
|
defaultConsistencyModel: "authoritative",
|
|
54683
56274
|
capabilities: [
|
|
54684
|
-
|
|
54685
|
-
|
|
56275
|
+
readCapability8("workspaces.list", "List Zoho Bookings workspaces.", {}),
|
|
56276
|
+
readCapability8("services.list", "List services, optionally within one workspace.", {
|
|
54686
56277
|
workspace_id: { type: "string" }
|
|
54687
56278
|
}),
|
|
54688
|
-
|
|
56279
|
+
readCapability8("resources.list", "List bookable resources, optionally for one service.", {
|
|
54689
56280
|
service_id: { type: "string" }
|
|
54690
56281
|
}),
|
|
54691
|
-
|
|
56282
|
+
readCapability8("staff.list", "List staff, optionally for one service.", {
|
|
54692
56283
|
service_id: { type: "string" }
|
|
54693
56284
|
}),
|
|
54694
|
-
|
|
56285
|
+
readCapability8("appointments.list", "List appointments from a required start time.", {
|
|
54695
56286
|
from_time: {
|
|
54696
56287
|
type: "string",
|
|
54697
56288
|
description: "Zoho timestamp in dd-MMM-yyyy HH:mm:ss format."
|
|
@@ -54701,17 +56292,17 @@ var zohoBookingsConnector = {
|
|
|
54701
56292
|
staff_id: { type: "string" },
|
|
54702
56293
|
status: { type: "string" }
|
|
54703
56294
|
}, ["from_time"]),
|
|
54704
|
-
|
|
56295
|
+
readCapability8("appointments.get", "Read one appointment by booking id.", {
|
|
54705
56296
|
booking_id: { type: "string" }
|
|
54706
56297
|
}, ["booking_id"]),
|
|
54707
|
-
|
|
56298
|
+
readCapability8("availability.fetch", "Fetch open time slots for a service and date.", {
|
|
54708
56299
|
service_id: { type: "string" },
|
|
54709
56300
|
selected_date: { type: "string", description: "Date in YYYY-MM-DD format." },
|
|
54710
56301
|
staff_id: { type: "string" },
|
|
54711
56302
|
group_id: { type: "string" },
|
|
54712
56303
|
resource_id: { type: "string" }
|
|
54713
56304
|
}, ["service_id", "selected_date"]),
|
|
54714
|
-
|
|
56305
|
+
mutationCapability8("appointments.book", "Book an appointment for a customer.", {
|
|
54715
56306
|
service_id: { type: "string" },
|
|
54716
56307
|
from_time: { type: "string", description: "Zoho timestamp in dd-MMM-yyyy HH:mm:ss format." },
|
|
54717
56308
|
customer_details: {
|
|
@@ -54727,13 +56318,13 @@ var zohoBookingsConnector = {
|
|
|
54727
56318
|
additional_fields: { type: "object" },
|
|
54728
56319
|
payment_info: { type: "object" }
|
|
54729
56320
|
}, ["service_id", "from_time", "customer_details"]),
|
|
54730
|
-
|
|
56321
|
+
mutationCapability8("appointments.reschedule", "Move an appointment to a new time, staff member, or group.", {
|
|
54731
56322
|
booking_id: { type: "string" },
|
|
54732
56323
|
start_time: { type: "string", description: "Zoho timestamp in dd-MMM-yyyy HH:mm:ss format." },
|
|
54733
56324
|
staff_id: { type: "string" },
|
|
54734
56325
|
group_id: { type: "string" }
|
|
54735
56326
|
}, ["booking_id"]),
|
|
54736
|
-
|
|
56327
|
+
mutationCapability8("appointments.cancel", "Cancel an appointment by booking id.", {
|
|
54737
56328
|
booking_id: { type: "string" }
|
|
54738
56329
|
}, ["booking_id"])
|
|
54739
56330
|
]
|
|
@@ -54850,7 +56441,7 @@ var zohoBookingsConnector = {
|
|
|
54850
56441
|
}
|
|
54851
56442
|
}
|
|
54852
56443
|
};
|
|
54853
|
-
function
|
|
56444
|
+
function readCapability8(name, description, properties, required = []) {
|
|
54854
56445
|
return {
|
|
54855
56446
|
name,
|
|
54856
56447
|
class: "read",
|
|
@@ -54859,7 +56450,7 @@ function readCapability5(name, description, properties, required = []) {
|
|
|
54859
56450
|
requiredScopes: ["zohobookings.data.READ"]
|
|
54860
56451
|
};
|
|
54861
56452
|
}
|
|
54862
|
-
function
|
|
56453
|
+
function mutationCapability8(name, description, properties, required) {
|
|
54863
56454
|
return {
|
|
54864
56455
|
name,
|
|
54865
56456
|
class: "mutation",
|
|
@@ -62203,6 +63794,393 @@ var metabaseConnector = declarativeRestConnector({
|
|
|
62203
63794
|
]
|
|
62204
63795
|
});
|
|
62205
63796
|
|
|
63797
|
+
// src/connectors/adapters/mysql.ts
|
|
63798
|
+
import { connect as createSocket, isIP as isIP5 } from "net";
|
|
63799
|
+
import {
|
|
63800
|
+
createConnection as createMysqlConnection
|
|
63801
|
+
} from "mysql2/promise";
|
|
63802
|
+
var MAX_PARAMETERS = 1e3;
|
|
63803
|
+
var MAX_PARAMETER_BYTES = 10 * 1024 * 1024;
|
|
63804
|
+
var MAX_RESULT_ROWS = 1e4;
|
|
63805
|
+
var MAX_RESULT_BYTES2 = 10 * 1024 * 1024;
|
|
63806
|
+
var DEFAULT_QUERY_TIMEOUT_MS = 3e4;
|
|
63807
|
+
function createMySqlConnector(options = {}) {
|
|
63808
|
+
const createConnection = options.createConnection ?? defaultCreateConnection;
|
|
63809
|
+
const resolveHost = options.resolveHost ?? resolvePublicHostAddresses;
|
|
63810
|
+
return {
|
|
63811
|
+
manifest: {
|
|
63812
|
+
kind: "mysql",
|
|
63813
|
+
displayName: "MySQL",
|
|
63814
|
+
description: "Inspect schemas and run bounded parameterized reads or approved compare-and-swap writes against MySQL over verified TLS.",
|
|
63815
|
+
auth: {
|
|
63816
|
+
kind: "api-key",
|
|
63817
|
+
hint: "JSON with host, port, user, password, optional database, and optional tlsCa. TLS and public network targets are mandatory."
|
|
63818
|
+
},
|
|
63819
|
+
defaultConsistencyModel: "authoritative",
|
|
63820
|
+
category: "database",
|
|
63821
|
+
rateLimit: { requests: 120, windowMs: 6e4, scope: "data-source" },
|
|
63822
|
+
capabilities: [
|
|
63823
|
+
{
|
|
63824
|
+
name: "mysql.databases.list",
|
|
63825
|
+
class: "read",
|
|
63826
|
+
description: "List databases visible to the connected MySQL user.",
|
|
63827
|
+
parameters: emptySchema2()
|
|
63828
|
+
},
|
|
63829
|
+
{
|
|
63830
|
+
name: "mysql.tables.list",
|
|
63831
|
+
class: "read",
|
|
63832
|
+
description: "List base tables and views in one database.",
|
|
63833
|
+
parameters: {
|
|
63834
|
+
type: "object",
|
|
63835
|
+
properties: { database: identifierSchema("Database name; defaults to the connection database.") },
|
|
63836
|
+
additionalProperties: false
|
|
63837
|
+
}
|
|
63838
|
+
},
|
|
63839
|
+
{
|
|
63840
|
+
name: "mysql.tables.describe",
|
|
63841
|
+
class: "read",
|
|
63842
|
+
description: "Read columns, types, nullability, defaults, and keys for one table.",
|
|
63843
|
+
parameters: {
|
|
63844
|
+
type: "object",
|
|
63845
|
+
properties: {
|
|
63846
|
+
database: identifierSchema("Database name; defaults to the connection database."),
|
|
63847
|
+
table: identifierSchema("Table name.")
|
|
63848
|
+
},
|
|
63849
|
+
required: ["table"],
|
|
63850
|
+
additionalProperties: false
|
|
63851
|
+
}
|
|
63852
|
+
},
|
|
63853
|
+
{
|
|
63854
|
+
name: "mysql.query",
|
|
63855
|
+
class: "read",
|
|
63856
|
+
description: "Run one parameterized SELECT, SHOW, DESCRIBE, or EXPLAIN statement inside a read-only transaction.",
|
|
63857
|
+
parameters: statementSchema(false)
|
|
63858
|
+
},
|
|
63859
|
+
{
|
|
63860
|
+
name: "mysql.execute",
|
|
63861
|
+
class: "mutation",
|
|
63862
|
+
description: "Run one approved parameterized INSERT, UPDATE, DELETE, or REPLACE statement and commit only when the affected-row count matches the caller expectation.",
|
|
63863
|
+
parameters: statementSchema(true),
|
|
63864
|
+
cas: "optimistic-read-verify",
|
|
63865
|
+
externalEffect: true
|
|
63866
|
+
}
|
|
63867
|
+
]
|
|
63868
|
+
},
|
|
63869
|
+
async test(source) {
|
|
63870
|
+
try {
|
|
63871
|
+
await withConnection(source, createConnection, resolveHost, async (connection) => {
|
|
63872
|
+
await connection.ping();
|
|
63873
|
+
});
|
|
63874
|
+
return { ok: true };
|
|
63875
|
+
} catch (error) {
|
|
63876
|
+
return { ok: false, reason: safeErrorMessage3(error) };
|
|
63877
|
+
}
|
|
63878
|
+
},
|
|
63879
|
+
async executeRead(inv) {
|
|
63880
|
+
const query2 = readQuery2(inv);
|
|
63881
|
+
assertReadStatement(query2.statement);
|
|
63882
|
+
const data4 = await withConnection(inv.source, createConnection, resolveHost, async (connection) => {
|
|
63883
|
+
await executeStatement(connection, {
|
|
63884
|
+
statement: "START TRANSACTION READ ONLY",
|
|
63885
|
+
parameters: []
|
|
63886
|
+
}, queryTimeout(inv.source));
|
|
63887
|
+
try {
|
|
63888
|
+
const [rows, fields] = await executeStatement(connection, query2, queryTimeout(inv.source));
|
|
63889
|
+
if (!Array.isArray(rows)) throw new Error("MySQL read returned a non-row result");
|
|
63890
|
+
return boundedResult(rows, fields);
|
|
63891
|
+
} finally {
|
|
63892
|
+
await connection.rollback().catch(() => void 0);
|
|
63893
|
+
}
|
|
63894
|
+
});
|
|
63895
|
+
return { data: data4, fetchedAt: Date.now() };
|
|
63896
|
+
},
|
|
63897
|
+
async executeMutation(inv) {
|
|
63898
|
+
if (inv.capabilityName !== "mysql.execute") {
|
|
63899
|
+
throw new Error(`Unknown MySQL mutation capability: ${inv.capabilityName}`);
|
|
63900
|
+
}
|
|
63901
|
+
const query2 = readStatementArgs(inv.args);
|
|
63902
|
+
assertMutationStatement(query2.statement);
|
|
63903
|
+
const expectedAffectedRows = readBoundedInteger(
|
|
63904
|
+
inv.args.expectedAffectedRows,
|
|
63905
|
+
-1,
|
|
63906
|
+
0,
|
|
63907
|
+
MAX_RESULT_ROWS,
|
|
63908
|
+
"expectedAffectedRows"
|
|
63909
|
+
);
|
|
63910
|
+
if (expectedAffectedRows < 0) throw new Error("expectedAffectedRows is required");
|
|
63911
|
+
return withConnection(inv.source, createConnection, resolveHost, async (connection) => {
|
|
63912
|
+
await connection.beginTransaction();
|
|
63913
|
+
try {
|
|
63914
|
+
const [result] = await executeStatement(connection, query2, queryTimeout(inv.source));
|
|
63915
|
+
const affectedRows = readAffectedRows(result);
|
|
63916
|
+
if (affectedRows !== expectedAffectedRows) {
|
|
63917
|
+
await connection.rollback();
|
|
63918
|
+
return {
|
|
63919
|
+
status: "conflict",
|
|
63920
|
+
alternatives: [],
|
|
63921
|
+
currentState: { affectedRows },
|
|
63922
|
+
message: `MySQL write affected ${affectedRows} rows; expected ${expectedAffectedRows}. The transaction was rolled back.`
|
|
63923
|
+
};
|
|
63924
|
+
}
|
|
63925
|
+
await connection.commit();
|
|
63926
|
+
return committedMutation(result, affectedRows);
|
|
63927
|
+
} catch (error) {
|
|
63928
|
+
await connection.rollback().catch(() => void 0);
|
|
63929
|
+
throw error;
|
|
63930
|
+
}
|
|
63931
|
+
});
|
|
63932
|
+
}
|
|
63933
|
+
};
|
|
63934
|
+
}
|
|
63935
|
+
var mysqlConnector = createMySqlConnector();
|
|
63936
|
+
async function defaultCreateConnection(config) {
|
|
63937
|
+
return createMysqlConnection(config);
|
|
63938
|
+
}
|
|
63939
|
+
async function withConnection(source, createConnection, resolveHost, run) {
|
|
63940
|
+
const credentials = readCredentials5(source);
|
|
63941
|
+
const addresses = await resolveHost(credentials.host);
|
|
63942
|
+
if (addresses.length === 0) throw new Error("MySQL host did not resolve");
|
|
63943
|
+
const address = addresses[0];
|
|
63944
|
+
let connection;
|
|
63945
|
+
try {
|
|
63946
|
+
connection = await createConnection({
|
|
63947
|
+
host: credentials.host,
|
|
63948
|
+
port: credentials.port,
|
|
63949
|
+
user: credentials.user,
|
|
63950
|
+
password: credentials.password,
|
|
63951
|
+
database: credentials.database,
|
|
63952
|
+
connectTimeout: credentials.connectTimeoutMs,
|
|
63953
|
+
multipleStatements: false,
|
|
63954
|
+
namedPlaceholders: false,
|
|
63955
|
+
supportBigNumbers: true,
|
|
63956
|
+
bigNumberStrings: true,
|
|
63957
|
+
decimalNumbers: false,
|
|
63958
|
+
dateStrings: true,
|
|
63959
|
+
timezone: "Z",
|
|
63960
|
+
charset: "utf8mb4",
|
|
63961
|
+
enableKeepAlive: true,
|
|
63962
|
+
maxPreparedStatements: 100,
|
|
63963
|
+
stream: () => createSocket({ host: address, port: credentials.port }),
|
|
63964
|
+
ssl: {
|
|
63965
|
+
ca: credentials.tlsCa,
|
|
63966
|
+
minVersion: "TLSv1.2",
|
|
63967
|
+
rejectUnauthorized: true,
|
|
63968
|
+
verifyIdentity: true
|
|
63969
|
+
}
|
|
63970
|
+
});
|
|
63971
|
+
return await run(connection);
|
|
63972
|
+
} finally {
|
|
63973
|
+
if (connection) await connection.end().catch(() => void 0);
|
|
63974
|
+
}
|
|
63975
|
+
}
|
|
63976
|
+
function readCredentials5(source) {
|
|
63977
|
+
let raw;
|
|
63978
|
+
if (source.credentials.kind === "custom") raw = source.credentials.values;
|
|
63979
|
+
else if (source.credentials.kind === "api-key") {
|
|
63980
|
+
try {
|
|
63981
|
+
raw = JSON.parse(source.credentials.apiKey);
|
|
63982
|
+
} catch {
|
|
63983
|
+
throw new Error("MySQL credential must be valid JSON");
|
|
63984
|
+
}
|
|
63985
|
+
} else {
|
|
63986
|
+
throw new Error("MySQL requires a structured credential bundle");
|
|
63987
|
+
}
|
|
63988
|
+
if (!isPlainRecord(raw)) throw new Error("MySQL credential must be a JSON object");
|
|
63989
|
+
const host = requiredCredentialString3(raw.host, "host", 253);
|
|
63990
|
+
validateHost(host);
|
|
63991
|
+
const user = requiredCredentialString3(raw.user ?? raw.username, "user", 128);
|
|
63992
|
+
const password = requiredCredentialString3(raw.password, "password", 4096);
|
|
63993
|
+
const database = readOptionalString(raw.database, "database");
|
|
63994
|
+
if (database && database.length > 64) throw new Error("MySQL credential database exceeds 64 characters");
|
|
63995
|
+
const tlsCa = readOptionalString(raw.tlsCa, "tlsCa");
|
|
63996
|
+
if (tlsCa && Buffer.byteLength(tlsCa, "utf8") > 1024 * 1024) {
|
|
63997
|
+
throw new Error("MySQL credential tlsCa exceeds the 1 MiB limit");
|
|
63998
|
+
}
|
|
63999
|
+
return {
|
|
64000
|
+
host,
|
|
64001
|
+
port: readBoundedInteger(raw.port, 3306, 1, 65535, "port"),
|
|
64002
|
+
user,
|
|
64003
|
+
password,
|
|
64004
|
+
database,
|
|
64005
|
+
tlsCa,
|
|
64006
|
+
connectTimeoutMs: readBoundedInteger(raw.connectTimeoutMs, 1e4, 1e3, 3e4, "connectTimeoutMs"),
|
|
64007
|
+
queryTimeoutMs: readBoundedInteger(raw.queryTimeoutMs, DEFAULT_QUERY_TIMEOUT_MS, 1e3, 6e4, "queryTimeoutMs")
|
|
64008
|
+
};
|
|
64009
|
+
}
|
|
64010
|
+
function readQuery2(inv) {
|
|
64011
|
+
if (inv.capabilityName === "mysql.databases.list") {
|
|
64012
|
+
return {
|
|
64013
|
+
statement: "SELECT SCHEMA_NAME AS databaseName, DEFAULT_CHARACTER_SET_NAME AS characterSet, DEFAULT_COLLATION_NAME AS collation FROM information_schema.SCHEMATA ORDER BY SCHEMA_NAME",
|
|
64014
|
+
parameters: []
|
|
64015
|
+
};
|
|
64016
|
+
}
|
|
64017
|
+
if (inv.capabilityName === "mysql.tables.list") {
|
|
64018
|
+
const database = readDatabase(inv.args.database, inv.source);
|
|
64019
|
+
return {
|
|
64020
|
+
statement: "SELECT TABLE_NAME AS tableName, TABLE_TYPE AS tableType, ENGINE AS engine, TABLE_ROWS AS estimatedRows, TABLE_COLLATION AS collation FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? ORDER BY TABLE_NAME",
|
|
64021
|
+
parameters: [database]
|
|
64022
|
+
};
|
|
64023
|
+
}
|
|
64024
|
+
if (inv.capabilityName === "mysql.tables.describe") {
|
|
64025
|
+
const database = readDatabase(inv.args.database, inv.source);
|
|
64026
|
+
const table = readIdentifier(inv.args.table, "table");
|
|
64027
|
+
return {
|
|
64028
|
+
statement: "SELECT COLUMN_NAME AS columnName, ORDINAL_POSITION AS ordinalPosition, COLUMN_DEFAULT AS columnDefault, IS_NULLABLE AS isNullable, DATA_TYPE AS dataType, COLUMN_TYPE AS columnType, COLUMN_KEY AS columnKey, EXTRA AS extra FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION",
|
|
64029
|
+
parameters: [database, table]
|
|
64030
|
+
};
|
|
64031
|
+
}
|
|
64032
|
+
if (inv.capabilityName === "mysql.query") return readStatementArgs(inv.args);
|
|
64033
|
+
throw new Error(`Unknown MySQL read capability: ${inv.capabilityName}`);
|
|
64034
|
+
}
|
|
64035
|
+
function readStatementArgs(args) {
|
|
64036
|
+
const statement = requiredString7(args.statement, "statement");
|
|
64037
|
+
if (Buffer.byteLength(statement, "utf8") > 1024 * 1024) throw new Error("statement exceeds the 1 MiB limit");
|
|
64038
|
+
const parameters = args.parameters === void 0 ? [] : args.parameters;
|
|
64039
|
+
if (!Array.isArray(parameters) || parameters.length > MAX_PARAMETERS) {
|
|
64040
|
+
throw new Error(`parameters must be an array with at most ${MAX_PARAMETERS} entries`);
|
|
64041
|
+
}
|
|
64042
|
+
for (const [index, value] of parameters.entries()) {
|
|
64043
|
+
if (value !== null && !["string", "number", "boolean"].includes(typeof value)) {
|
|
64044
|
+
throw new Error(`parameters[${index}] must be a JSON scalar or null`);
|
|
64045
|
+
}
|
|
64046
|
+
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
64047
|
+
throw new Error(`parameters[${index}] must be finite`);
|
|
64048
|
+
}
|
|
64049
|
+
}
|
|
64050
|
+
if (Buffer.byteLength(JSON.stringify(parameters), "utf8") > MAX_PARAMETER_BYTES) {
|
|
64051
|
+
throw new Error(`parameters exceed the ${MAX_PARAMETER_BYTES}-byte limit`);
|
|
64052
|
+
}
|
|
64053
|
+
return { statement, parameters };
|
|
64054
|
+
}
|
|
64055
|
+
function assertReadStatement(statement) {
|
|
64056
|
+
const keyword = leadingKeyword(statement);
|
|
64057
|
+
if (!["SELECT", "SHOW", "DESCRIBE", "DESC", "EXPLAIN", "WITH"].includes(keyword)) {
|
|
64058
|
+
throw new Error("mysql.query accepts only SELECT, SHOW, DESCRIBE, or EXPLAIN statements");
|
|
64059
|
+
}
|
|
64060
|
+
if (/\b(?:INTO\s+(?:OUTFILE|DUMPFILE)|LOAD_FILE\s*\(|GET_LOCK\s*\(|RELEASE_LOCK\s*\(|SLEEP\s*\(|BENCHMARK\s*\(|FOR\s+UPDATE|LOCK\s+IN\s+SHARE\s+MODE)\b/i.test(statement)) {
|
|
64061
|
+
throw new Error("mysql.query rejects file, lock, and delay side effects");
|
|
64062
|
+
}
|
|
64063
|
+
}
|
|
64064
|
+
function assertMutationStatement(statement) {
|
|
64065
|
+
const keyword = leadingKeyword(statement);
|
|
64066
|
+
if (!["INSERT", "UPDATE", "DELETE", "REPLACE"].includes(keyword)) {
|
|
64067
|
+
throw new Error("mysql.execute accepts only INSERT, UPDATE, DELETE, or REPLACE statements");
|
|
64068
|
+
}
|
|
64069
|
+
}
|
|
64070
|
+
function leadingKeyword(statement) {
|
|
64071
|
+
const withoutComments = statement.replace(/^\s*(?:--[^\n]*(?:\n|$)|#[^\n]*(?:\n|$)|\/\*[\s\S]*?\*\/\s*)*/u, "").trimStart();
|
|
64072
|
+
return /^[A-Za-z]+/.exec(withoutComments)?.[0]?.toUpperCase() ?? "";
|
|
64073
|
+
}
|
|
64074
|
+
async function executeStatement(connection, query2, timeout) {
|
|
64075
|
+
return connection.execute({ sql: query2.statement, values: query2.parameters, timeout });
|
|
64076
|
+
}
|
|
64077
|
+
function boundedResult(rows, fields) {
|
|
64078
|
+
if (rows.length > MAX_RESULT_ROWS) throw new Error(`MySQL result exceeds ${MAX_RESULT_ROWS} rows`);
|
|
64079
|
+
const data4 = {
|
|
64080
|
+
rows: jsonSafe(rows),
|
|
64081
|
+
columns: fields.map((field) => ({
|
|
64082
|
+
name: field.name,
|
|
64083
|
+
table: field.table,
|
|
64084
|
+
database: field.schema,
|
|
64085
|
+
type: field.type
|
|
64086
|
+
})),
|
|
64087
|
+
rowCount: rows.length
|
|
64088
|
+
};
|
|
64089
|
+
const serialized = JSON.stringify(data4);
|
|
64090
|
+
if (Buffer.byteLength(serialized, "utf8") > MAX_RESULT_BYTES2) {
|
|
64091
|
+
throw new Error(`MySQL result exceeds the ${MAX_RESULT_BYTES2}-byte limit`);
|
|
64092
|
+
}
|
|
64093
|
+
return data4;
|
|
64094
|
+
}
|
|
64095
|
+
function readAffectedRows(result) {
|
|
64096
|
+
if (!isPlainRecord(result) || !Number.isInteger(result.affectedRows) || result.affectedRows < 0) {
|
|
64097
|
+
throw new Error("MySQL mutation did not return an affected-row count");
|
|
64098
|
+
}
|
|
64099
|
+
return result.affectedRows;
|
|
64100
|
+
}
|
|
64101
|
+
function committedMutation(result, affectedRows) {
|
|
64102
|
+
const header2 = result;
|
|
64103
|
+
const rawInsertId = header2.insertId;
|
|
64104
|
+
const insertId = typeof rawInsertId === "number" && Number.isSafeInteger(rawInsertId) ? rawInsertId : typeof rawInsertId === "string" ? rawInsertId : void 0;
|
|
64105
|
+
return {
|
|
64106
|
+
status: "committed",
|
|
64107
|
+
data: jsonSafe({
|
|
64108
|
+
affectedRows,
|
|
64109
|
+
changedRows: header2.changedRows,
|
|
64110
|
+
insertId,
|
|
64111
|
+
warningStatus: header2.warningStatus
|
|
64112
|
+
}),
|
|
64113
|
+
committedAt: Date.now(),
|
|
64114
|
+
idempotentReplay: false
|
|
64115
|
+
};
|
|
64116
|
+
}
|
|
64117
|
+
function readDatabase(value, source) {
|
|
64118
|
+
const explicit = value === void 0 ? void 0 : readIdentifier(value, "database");
|
|
64119
|
+
if (explicit) return explicit;
|
|
64120
|
+
const credentials = readCredentials5(source);
|
|
64121
|
+
if (!credentials.database) throw new Error("database is required when the connection has no default database");
|
|
64122
|
+
return credentials.database;
|
|
64123
|
+
}
|
|
64124
|
+
function queryTimeout(source) {
|
|
64125
|
+
return readCredentials5(source).queryTimeoutMs;
|
|
64126
|
+
}
|
|
64127
|
+
function requiredString7(value, label) {
|
|
64128
|
+
const parsed = readOptionalString(value, label);
|
|
64129
|
+
if (!parsed) throw new Error(`${label} is required`);
|
|
64130
|
+
return parsed;
|
|
64131
|
+
}
|
|
64132
|
+
function requiredCredentialString3(value, label, maxLength) {
|
|
64133
|
+
const parsed = readOptionalString(value, label);
|
|
64134
|
+
if (!parsed) throw new Error(`MySQL credential ${label} is required`);
|
|
64135
|
+
if (parsed.length > maxLength) throw new Error(`MySQL credential ${label} exceeds ${maxLength} characters`);
|
|
64136
|
+
return parsed;
|
|
64137
|
+
}
|
|
64138
|
+
function readIdentifier(value, label) {
|
|
64139
|
+
const parsed = requiredString7(value, label);
|
|
64140
|
+
if (parsed.length > 64) throw new Error(`${label} exceeds 64 characters`);
|
|
64141
|
+
return parsed;
|
|
64142
|
+
}
|
|
64143
|
+
function validateHost(host) {
|
|
64144
|
+
const normalized = host.toLowerCase();
|
|
64145
|
+
if (host.length > 253 || host.includes("://") || /[\s/@]/.test(host) || normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".local") || isIP5(host) === 0 && !/^[A-Za-z0-9.-]+$/.test(host) || host.split(".").some((label) => !label || label.startsWith("-") || label.endsWith("-"))) {
|
|
64146
|
+
throw new Error("MySQL host must be a public hostname or IP address without a scheme");
|
|
64147
|
+
}
|
|
64148
|
+
}
|
|
64149
|
+
function emptySchema2() {
|
|
64150
|
+
return { type: "object", properties: {}, additionalProperties: false };
|
|
64151
|
+
}
|
|
64152
|
+
function identifierSchema(description) {
|
|
64153
|
+
return { type: "string", minLength: 1, maxLength: 64, description };
|
|
64154
|
+
}
|
|
64155
|
+
function statementSchema(mutation) {
|
|
64156
|
+
const properties = {
|
|
64157
|
+
statement: { type: "string", minLength: 1, maxLength: 1024 * 1024 },
|
|
64158
|
+
parameters: {
|
|
64159
|
+
type: "array",
|
|
64160
|
+
maxItems: MAX_PARAMETERS,
|
|
64161
|
+
items: { type: ["string", "number", "boolean", "null"] },
|
|
64162
|
+
description: "Values bound to ? placeholders in order."
|
|
64163
|
+
}
|
|
64164
|
+
};
|
|
64165
|
+
if (mutation) {
|
|
64166
|
+
properties.expectedAffectedRows = {
|
|
64167
|
+
type: "integer",
|
|
64168
|
+
minimum: 0,
|
|
64169
|
+
maximum: MAX_RESULT_ROWS,
|
|
64170
|
+
description: "Commit only when MySQL reports exactly this many affected rows."
|
|
64171
|
+
};
|
|
64172
|
+
}
|
|
64173
|
+
return {
|
|
64174
|
+
type: "object",
|
|
64175
|
+
properties,
|
|
64176
|
+
required: mutation ? ["statement", "expectedAffectedRows"] : ["statement"],
|
|
64177
|
+
additionalProperties: false
|
|
64178
|
+
};
|
|
64179
|
+
}
|
|
64180
|
+
function safeErrorMessage3(error) {
|
|
64181
|
+
return error instanceof Error && error.message ? error.message : "MySQL connection failed";
|
|
64182
|
+
}
|
|
64183
|
+
|
|
62206
64184
|
// src/connectors/adapters/segment.ts
|
|
62207
64185
|
var segmentConnector = declarativeRestConnector({
|
|
62208
64186
|
kind: "segment",
|
|
@@ -63698,6 +65676,7 @@ var CONNECTOR_ADAPTER_FACTORIES = [
|
|
|
63698
65676
|
defineFactoryAdapter(() => digitalOceanConnector, {}),
|
|
63699
65677
|
defineFactoryAdapter(() => clicksendConnector, {}),
|
|
63700
65678
|
defineFactoryAdapter(() => metabaseConnector, {}),
|
|
65679
|
+
defineFactoryAdapter(() => mysqlConnector, {}),
|
|
63701
65680
|
defineFactoryAdapter(
|
|
63702
65681
|
() => microsoftDynamics365BusinessCentralConnector,
|
|
63703
65682
|
microsoftOAuthEnvMap
|
|
@@ -63755,6 +65734,10 @@ var CONNECTOR_ADAPTER_FACTORIES = [
|
|
|
63755
65734
|
defineFactoryAdapter(() => amazonSqsConnector, {}),
|
|
63756
65735
|
defineFactoryAdapter(() => httpConnector, {}),
|
|
63757
65736
|
defineFactoryAdapter(() => rssConnector, {}),
|
|
65737
|
+
defineFactoryAdapter(() => kafkaConnector, {}),
|
|
65738
|
+
defineFactoryAdapter(() => duckdbConnector, {}),
|
|
65739
|
+
defineFactoryAdapter(() => rabbitMqConnector, {}),
|
|
65740
|
+
defineFactoryAdapter(() => redisConnector, {}),
|
|
63758
65741
|
defineFactoryAdapter(() => csvFilesConnector, {}),
|
|
63759
65742
|
defineFactoryAdapter(() => excelFilesConnector, {}),
|
|
63760
65743
|
defineFactoryAdapter(() => parquetFilesConnector, {}),
|
|
@@ -63827,10 +65810,7 @@ var CONNECTOR_ADAPTER_FACTORIES = [
|
|
|
63827
65810
|
clientId: "MONDAY_OAUTH_CLIENT_ID",
|
|
63828
65811
|
clientSecret: "MONDAY_OAUTH_CLIENT_SECRET"
|
|
63829
65812
|
}),
|
|
63830
|
-
defineFactoryAdapter(() => clickupConnector, {
|
|
63831
|
-
clientId: "CLICKUP_OAUTH_CLIENT_ID",
|
|
63832
|
-
clientSecret: "CLICKUP_OAUTH_CLIENT_SECRET"
|
|
63833
|
-
}),
|
|
65813
|
+
defineFactoryAdapter(() => clickupConnector, {}),
|
|
63834
65814
|
defineFactoryAdapter(() => basecampConnector, {
|
|
63835
65815
|
clientId: "BASECAMP_OAUTH_CLIENT_ID",
|
|
63836
65816
|
clientSecret: "BASECAMP_OAUTH_CLIENT_SECRET"
|
|
@@ -69735,7 +71715,7 @@ var sanityConnector = declarativeRestConnector({
|
|
|
69735
71715
|
var figmaConnector = declarativeRestConnector({
|
|
69736
71716
|
kind: "figma",
|
|
69737
71717
|
displayName: "Figma",
|
|
69738
|
-
description: "Read Figma files, components, comments, and
|
|
71718
|
+
description: "Read Figma files, components, comments, and project metadata; post comments back into the design source of truth.",
|
|
69739
71719
|
auth: {
|
|
69740
71720
|
kind: "oauth2",
|
|
69741
71721
|
authorizationUrl: "https://www.figma.com/oauth",
|
|
@@ -69748,7 +71728,7 @@ var figmaConnector = declarativeRestConnector({
|
|
|
69748
71728
|
"file_dev_resources:read",
|
|
69749
71729
|
"file_versions:read",
|
|
69750
71730
|
"library_content:read",
|
|
69751
|
-
"
|
|
71731
|
+
"project_metadata:read",
|
|
69752
71732
|
"team_library_content:read",
|
|
69753
71733
|
"webhooks:write"
|
|
69754
71734
|
],
|
|
@@ -69964,35 +71944,16 @@ var figmaConnector = declarativeRestConnector({
|
|
|
69964
71944
|
requiredScopes: ["file_comments:read"]
|
|
69965
71945
|
},
|
|
69966
71946
|
{
|
|
69967
|
-
name: "
|
|
71947
|
+
name: "projects.metadata.get",
|
|
69968
71948
|
class: "read",
|
|
69969
|
-
description: "
|
|
71949
|
+
description: "Get metadata for a Figma project visible to the authenticated user.",
|
|
69970
71950
|
parameters: {
|
|
69971
71951
|
type: "object",
|
|
69972
|
-
properties: {
|
|
69973
|
-
required: ["team_id"]
|
|
69974
|
-
},
|
|
69975
|
-
request: { method: "GET", path: "/v1/teams/{team_id}/projects" },
|
|
69976
|
-
requiredScopes: ["projects:read"]
|
|
69977
|
-
},
|
|
69978
|
-
{
|
|
69979
|
-
name: "projects.files.list",
|
|
69980
|
-
class: "read",
|
|
69981
|
-
description: "List Figma files inside a project.",
|
|
69982
|
-
parameters: {
|
|
69983
|
-
type: "object",
|
|
69984
|
-
properties: {
|
|
69985
|
-
project_id: { type: "string" },
|
|
69986
|
-
branch_data: { type: "boolean" }
|
|
69987
|
-
},
|
|
71952
|
+
properties: { project_id: { type: "string" } },
|
|
69988
71953
|
required: ["project_id"]
|
|
69989
71954
|
},
|
|
69990
|
-
request: {
|
|
69991
|
-
|
|
69992
|
-
path: "/v1/projects/{project_id}/files",
|
|
69993
|
-
query: { branch_data: "{branch_data}" }
|
|
69994
|
-
},
|
|
69995
|
-
requiredScopes: ["projects:read"]
|
|
71955
|
+
request: { method: "GET", path: "/v1/projects/{project_id}/meta" },
|
|
71956
|
+
requiredScopes: ["project_metadata:read"]
|
|
69996
71957
|
},
|
|
69997
71958
|
{
|
|
69998
71959
|
name: "teams.components.list",
|
|
@@ -70159,7 +72120,7 @@ var figmaConnector = declarativeRestConnector({
|
|
|
70159
72120
|
var figjamConnector = declarativeRestConnector({
|
|
70160
72121
|
kind: "figjam",
|
|
70161
72122
|
displayName: "FigJam",
|
|
70162
|
-
description: "Read FigJam boards (stickies, connectors, shapes, sections),
|
|
72123
|
+
description: "Read FigJam boards (stickies, connectors, shapes, sections), project metadata, render board images, and post or delete board comments through the Figma REST API.",
|
|
70163
72124
|
auth: {
|
|
70164
72125
|
kind: "oauth2",
|
|
70165
72126
|
authorizationUrl: "https://www.figma.com/oauth",
|
|
@@ -70170,7 +72131,7 @@ var figjamConnector = declarativeRestConnector({
|
|
|
70170
72131
|
"file_comments:write",
|
|
70171
72132
|
"file_content:read",
|
|
70172
72133
|
"file_versions:read",
|
|
70173
|
-
"
|
|
72134
|
+
"project_metadata:read",
|
|
70174
72135
|
"webhooks:write"
|
|
70175
72136
|
],
|
|
70176
72137
|
clientIdEnv: "FIGMA_OAUTH_CLIENT_ID",
|
|
@@ -70328,33 +72289,16 @@ var figjamConnector = declarativeRestConnector({
|
|
|
70328
72289
|
requiredScopes: ["file_comments:read"]
|
|
70329
72290
|
},
|
|
70330
72291
|
{
|
|
70331
|
-
name: "
|
|
70332
|
-
class: "read",
|
|
70333
|
-
description: "List projects inside a Figma team (FigJam boards live alongside Figma design files inside projects).",
|
|
70334
|
-
parameters: {
|
|
70335
|
-
type: "object",
|
|
70336
|
-
properties: { team_id: { type: "string" } },
|
|
70337
|
-
required: ["team_id"]
|
|
70338
|
-
},
|
|
70339
|
-
request: { method: "GET", path: "/v1/teams/{team_id}/projects" },
|
|
70340
|
-
requiredScopes: ["projects:read"]
|
|
70341
|
-
},
|
|
70342
|
-
{
|
|
70343
|
-
name: "projects.files.list",
|
|
72292
|
+
name: "projects.metadata.get",
|
|
70344
72293
|
class: "read",
|
|
70345
|
-
description:
|
|
72294
|
+
description: "Get metadata for a Figma/FigJam project visible to the authenticated user.",
|
|
70346
72295
|
parameters: {
|
|
70347
72296
|
type: "object",
|
|
70348
|
-
properties: {
|
|
70349
|
-
project_id: { type: "string" }
|
|
70350
|
-
},
|
|
72297
|
+
properties: { project_id: { type: "string" } },
|
|
70351
72298
|
required: ["project_id"]
|
|
70352
72299
|
},
|
|
70353
|
-
request: {
|
|
70354
|
-
|
|
70355
|
-
path: "/v1/projects/{project_id}/files"
|
|
70356
|
-
},
|
|
70357
|
-
requiredScopes: ["projects:read"]
|
|
72300
|
+
request: { method: "GET", path: "/v1/projects/{project_id}/meta" },
|
|
72301
|
+
requiredScopes: ["project_metadata:read"]
|
|
70358
72302
|
},
|
|
70359
72303
|
{
|
|
70360
72304
|
name: "files.comments.create",
|
|
@@ -143646,6 +145590,7 @@ export {
|
|
|
143646
145590
|
deepgramConnector,
|
|
143647
145591
|
detectingAiConnector,
|
|
143648
145592
|
digitalPilotConnector,
|
|
145593
|
+
duckdbConnector,
|
|
143649
145594
|
docsbotConnector,
|
|
143650
145595
|
dripConnector,
|
|
143651
145596
|
echowinConnector,
|
|
@@ -143754,6 +145699,9 @@ export {
|
|
|
143754
145699
|
jiraCloudConnector,
|
|
143755
145700
|
jotformConnector,
|
|
143756
145701
|
justcallConnector,
|
|
145702
|
+
createKafkaConnector,
|
|
145703
|
+
kafkaConnector,
|
|
145704
|
+
publicKafkaSocketFactory,
|
|
143757
145705
|
leadConnectorConnector,
|
|
143758
145706
|
lemonSqueezyConnector,
|
|
143759
145707
|
linearConnector,
|
|
@@ -143791,6 +145739,10 @@ export {
|
|
|
143791
145739
|
pipedriveConnector,
|
|
143792
145740
|
parquetFilesConnector,
|
|
143793
145741
|
quickbooksConnector,
|
|
145742
|
+
createRabbitMqConnector,
|
|
145743
|
+
rabbitMqConnector,
|
|
145744
|
+
createRedisConnector,
|
|
145745
|
+
redisConnector,
|
|
143794
145746
|
recallAiConnector,
|
|
143795
145747
|
rampConnector,
|
|
143796
145748
|
ringcentralConnector,
|
|
@@ -143858,6 +145810,8 @@ export {
|
|
|
143858
145810
|
googleBigqueryConnector,
|
|
143859
145811
|
hightouchConnector,
|
|
143860
145812
|
metabaseConnector,
|
|
145813
|
+
createMySqlConnector,
|
|
145814
|
+
mysqlConnector,
|
|
143861
145815
|
segmentConnector,
|
|
143862
145816
|
supabaseConnector,
|
|
143863
145817
|
builtwithConnector,
|
|
@@ -144249,4 +146203,4 @@ export {
|
|
|
144249
146203
|
demandbaseConnector,
|
|
144250
146204
|
adapters_exports
|
|
144251
146205
|
};
|
|
144252
|
-
//# sourceMappingURL=chunk-
|
|
146206
|
+
//# sourceMappingURL=chunk-FOIC32DF.js.map
|