@tangle-network/agent-integrations 0.53.30 → 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-DSIV6RU6.js → chunk-5MHN5LWD.js} +2 -2
- package/dist/{chunk-DQIIDGA2.js → chunk-7GS2RYBA.js} +3 -3
- package/dist/{chunk-5LDZLVKP.js → chunk-FOIC32DF.js} +1496 -38
- package/dist/{chunk-5LDZLVKP.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-UEJFZZ2E.js → chunk-UUXEF2QZ.js} +77 -4
- package/dist/chunk-UUXEF2QZ.js.map +1 -0
- package/dist/connectors/adapters/index.d.ts +69 -1
- package/dist/connectors/adapters/index.js +15 -1
- package/dist/connectors/index.d.ts +4 -1
- package/dist/connectors/index.js +16 -2
- package/dist/coverage-catalog.js +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +19 -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 +24 -19
- package/docs/integration-execution-matrix.json +391 -425
- package/package.json +16 -10
- package/dist/chunk-LBJJMBVE.js.map +0 -1
- package/dist/chunk-UEJFZZ2E.js.map +0 -1
- /package/dist/{chunk-DSIV6RU6.js.map → chunk-5MHN5LWD.js.map} +0 -0
- /package/dist/{chunk-DQIIDGA2.js.map → chunk-7GS2RYBA.js.map} +0 -0
|
@@ -182,6 +182,9 @@ __export(adapters_exports, {
|
|
|
182
182
|
copyAiConnector: () => copyAiConnector,
|
|
183
183
|
couchbaseConnector: () => couchbaseConnector,
|
|
184
184
|
createKafkaConnector: () => createKafkaConnector,
|
|
185
|
+
createMySqlConnector: () => createMySqlConnector,
|
|
186
|
+
createRabbitMqConnector: () => createRabbitMqConnector,
|
|
187
|
+
createRedisConnector: () => createRedisConnector,
|
|
185
188
|
createSftpConnector: () => createSftpConnector,
|
|
186
189
|
createTangleIdentityClient: () => createTangleIdentityClient,
|
|
187
190
|
crispConnector: () => crispConnector,
|
|
@@ -218,6 +221,7 @@ __export(adapters_exports, {
|
|
|
218
221
|
dripConnector: () => dripConnector,
|
|
219
222
|
dropboxConnector: () => dropboxConnector,
|
|
220
223
|
dubConnector: () => dubConnector,
|
|
224
|
+
duckdbConnector: () => duckdbConnector,
|
|
221
225
|
dumplingAiConnector: () => dumplingAiConnector,
|
|
222
226
|
dustConnector: () => dustConnector,
|
|
223
227
|
easyPeasyAiConnector: () => easyPeasyAiConnector,
|
|
@@ -438,6 +442,7 @@ __export(adapters_exports, {
|
|
|
438
442
|
mondayConnector: () => mondayConnector,
|
|
439
443
|
mongodbConnector: () => mongodbConnector,
|
|
440
444
|
mycaseConnector: () => mycaseConnector,
|
|
445
|
+
mysqlConnector: () => mysqlConnector,
|
|
441
446
|
n8nConnector: () => n8nConnector,
|
|
442
447
|
netlifyConnector: () => netlifyConnector,
|
|
443
448
|
netsuiteConnector: () => netsuiteConnector,
|
|
@@ -508,6 +513,7 @@ __export(adapters_exports, {
|
|
|
508
513
|
quadernoConnector: () => quadernoConnector,
|
|
509
514
|
quickbooksConnector: () => quickbooksConnector,
|
|
510
515
|
quickzuConnector: () => quickzuConnector,
|
|
516
|
+
rabbitMqConnector: () => rabbitMqConnector,
|
|
511
517
|
raiaAiConnector: () => raiaAiConnector,
|
|
512
518
|
rampConnector: () => rampConnector,
|
|
513
519
|
rapidtextAiConnector: () => rapidtextAiConnector,
|
|
@@ -515,6 +521,7 @@ __export(adapters_exports, {
|
|
|
515
521
|
recallAiConnector: () => recallAiConnector,
|
|
516
522
|
recurlyConnector: () => recurlyConnector,
|
|
517
523
|
redditConnector: () => redditConnector,
|
|
524
|
+
redisConnector: () => redisConnector,
|
|
518
525
|
reoonVerifierConnector: () => reoonVerifierConnector,
|
|
519
526
|
replyIoConnector: () => replyIoConnector,
|
|
520
527
|
resolveConnectorAdapterFactoryOptions: () => resolveConnectorAdapterFactoryOptions,
|
|
@@ -1123,6 +1130,10 @@ function sameUrl(left, right) {
|
|
|
1123
1130
|
function applyCredentials(headers, url, placement, credentials) {
|
|
1124
1131
|
const token = credentialToken(credentials);
|
|
1125
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
|
+
}
|
|
1126
1137
|
if (placement.kind === "basic-api-key") {
|
|
1127
1138
|
headers.authorization = `Basic ${Buffer.from(`${token}:`).toString("base64")}`;
|
|
1128
1139
|
}
|
|
@@ -6500,6 +6511,294 @@ var digitalPilotConnector = declarativeRestConnector({
|
|
|
6500
6511
|
]
|
|
6501
6512
|
});
|
|
6502
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
|
+
|
|
6503
6802
|
// src/connectors/adapters/docsbot.ts
|
|
6504
6803
|
var docsbotConnector = declarativeRestConnector({
|
|
6505
6804
|
kind: "docsbot",
|
|
@@ -6917,7 +7216,7 @@ var elevenlabsConnector = declarativeRestConnector({
|
|
|
6917
7216
|
// src/connectors/adapters/excel-files.ts
|
|
6918
7217
|
import readXlsxFile from "read-excel-file/node";
|
|
6919
7218
|
import writeXlsxFile from "write-excel-file/node";
|
|
6920
|
-
var
|
|
7219
|
+
var MAX_COLUMNS3 = 256;
|
|
6921
7220
|
var MAX_SHEETS = 50;
|
|
6922
7221
|
var MAX_UNCOMPRESSED_WORKBOOK_BYTES = 50 * 1024 * 1024;
|
|
6923
7222
|
var MAX_ZIP_ENTRIES = 1e3;
|
|
@@ -6958,7 +7257,7 @@ var excelFilesConnector = {
|
|
|
6958
7257
|
type: "array",
|
|
6959
7258
|
minItems: 1,
|
|
6960
7259
|
maxItems: MAX_RECORDS,
|
|
6961
|
-
items: { type: "array", maxItems:
|
|
7260
|
+
items: { type: "array", maxItems: MAX_COLUMNS3 }
|
|
6962
7261
|
},
|
|
6963
7262
|
sheetName: { type: "string", minLength: 1, maxLength: 31, default: "Sheet1" }
|
|
6964
7263
|
},
|
|
@@ -6988,8 +7287,8 @@ var excelFilesConnector = {
|
|
|
6988
7287
|
totalRows += sheet.data.length;
|
|
6989
7288
|
if (totalRows > MAX_RECORDS) throw new Error(`workbook exceeds the ${MAX_RECORDS}-row limit`);
|
|
6990
7289
|
for (const [rowIndex, row] of sheet.data.entries()) {
|
|
6991
|
-
if (row.length >
|
|
6992
|
-
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`);
|
|
6993
7292
|
}
|
|
6994
7293
|
}
|
|
6995
7294
|
return { name: sheet.sheet, rows: jsonSafe(sheet.data) };
|
|
@@ -7003,7 +7302,7 @@ var excelFilesConnector = {
|
|
|
7003
7302
|
if (capabilityName !== "excel.create") throw new Error(`Unknown Excel capability: ${capabilityName}`);
|
|
7004
7303
|
const rows = readRows(args.rows);
|
|
7005
7304
|
for (const [index, row] of rows.entries()) {
|
|
7006
|
-
if (row.length >
|
|
7305
|
+
if (row.length > MAX_COLUMNS3) throw new Error(`rows[${index}] exceeds the ${MAX_COLUMNS3}-column limit`);
|
|
7007
7306
|
}
|
|
7008
7307
|
const sheetName = readOptionalString(args.sheetName, "sheetName") ?? "Sheet1";
|
|
7009
7308
|
validateSheetName(sheetName);
|
|
@@ -16301,16 +16600,27 @@ var clickupConnector = declarativeRestConnector({
|
|
|
16301
16600
|
displayName: "ClickUp",
|
|
16302
16601
|
description: "Read and mutate ClickUp workspaces, spaces, folders, lists, tasks, comments, and time entries via the ClickUp REST v2 API.",
|
|
16303
16602
|
auth: {
|
|
16304
|
-
kind: "
|
|
16305
|
-
|
|
16306
|
-
|
|
16307
|
-
|
|
16308
|
-
|
|
16309
|
-
|
|
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
|
+
]
|
|
16310
16619
|
},
|
|
16311
16620
|
category: "other",
|
|
16312
16621
|
defaultConsistencyModel: "authoritative",
|
|
16313
16622
|
baseUrl: "https://api.clickup.com/api/v2",
|
|
16623
|
+
credentialPlacement: { kind: "authorization-by-auth" },
|
|
16314
16624
|
// GET /user — returns the authorized user; used as a cheap liveness probe.
|
|
16315
16625
|
test: { method: "GET", path: "/user" },
|
|
16316
16626
|
capabilities: [
|
|
@@ -43883,7 +44193,7 @@ var pipedriveConnector = declarativeRestConnector({
|
|
|
43883
44193
|
// src/connectors/adapters/parquet-files.ts
|
|
43884
44194
|
import { parquetReadObjects } from "hyparquet";
|
|
43885
44195
|
import { parquetWriteBuffer } from "hyparquet-writer";
|
|
43886
|
-
var
|
|
44196
|
+
var MAX_COLUMNS4 = 256;
|
|
43887
44197
|
var parquetFilesConnector = {
|
|
43888
44198
|
manifest: {
|
|
43889
44199
|
kind: "parquet-files",
|
|
@@ -43901,7 +44211,7 @@ var parquetFilesConnector = {
|
|
|
43901
44211
|
type: "object",
|
|
43902
44212
|
properties: {
|
|
43903
44213
|
fileBase64: { type: "string", description: "Parquet file content encoded as canonical base64." },
|
|
43904
|
-
columns: { type: "array", maxItems:
|
|
44214
|
+
columns: { type: "array", maxItems: MAX_COLUMNS4, items: { type: "string", minLength: 1 } },
|
|
43905
44215
|
rowStart: { type: "integer", minimum: 0, default: 0 },
|
|
43906
44216
|
rowLimit: { type: "integer", minimum: 1, maximum: MAX_RECORDS, default: MAX_RECORDS }
|
|
43907
44217
|
},
|
|
@@ -43947,8 +44257,8 @@ var parquetFilesConnector = {
|
|
|
43947
44257
|
if (capabilityName !== "parquet.create") throw new Error(`Unknown Parquet capability: ${capabilityName}`);
|
|
43948
44258
|
const records = readRecords(args.records);
|
|
43949
44259
|
const columnNames = [...new Set(records.flatMap((record) => Object.keys(record)))];
|
|
43950
|
-
if (columnNames.length === 0 || columnNames.length >
|
|
43951
|
-
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`);
|
|
43952
44262
|
}
|
|
43953
44263
|
if (columnNames.some((name) => name.length === 0)) throw new Error("column names must not be empty");
|
|
43954
44264
|
const columnData = columnNames.map((name) => {
|
|
@@ -43978,8 +44288,8 @@ function asyncBuffer(file) {
|
|
|
43978
44288
|
}
|
|
43979
44289
|
function readColumns2(value) {
|
|
43980
44290
|
if (value === void 0 || value === null) return void 0;
|
|
43981
|
-
if (!Array.isArray(value) || value.length === 0 || value.length >
|
|
43982
|
-
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`);
|
|
43983
44293
|
}
|
|
43984
44294
|
const columns = value.map((entry, index) => {
|
|
43985
44295
|
if (typeof entry !== "string" || entry.length === 0) throw new Error(`columns[${index}] must be a non-empty string`);
|
|
@@ -44435,6 +44745,759 @@ var quickbooksConnector = declarativeRestConnector({
|
|
|
44435
44745
|
]
|
|
44436
44746
|
});
|
|
44437
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
|
+
|
|
44438
45501
|
// src/connectors/adapters/recall-ai.ts
|
|
44439
45502
|
var recallAiRegions = [
|
|
44440
45503
|
"https://us-east-1.recall.ai",
|
|
@@ -45371,7 +46434,7 @@ function createSftpConnector(options = {}) {
|
|
|
45371
46434
|
]
|
|
45372
46435
|
},
|
|
45373
46436
|
async executeRead({ source, capabilityName, args }) {
|
|
45374
|
-
const credentials =
|
|
46437
|
+
const credentials = readCredentials4(source);
|
|
45375
46438
|
if (capabilityName === "sftp.list") {
|
|
45376
46439
|
const path = resolveRemotePath(credentials.rootPath, args.path ?? "/");
|
|
45377
46440
|
const limit = readBoundedInteger(args.limit, MAX_LIST_ENTRIES, 1, MAX_LIST_ENTRIES, "limit");
|
|
@@ -45404,7 +46467,7 @@ function createSftpConnector(options = {}) {
|
|
|
45404
46467
|
throw new Error(`Unknown SFTP read capability: ${capabilityName}`);
|
|
45405
46468
|
},
|
|
45406
46469
|
async executeMutation({ source, capabilityName, args }) {
|
|
45407
|
-
const credentials =
|
|
46470
|
+
const credentials = readCredentials4(source);
|
|
45408
46471
|
const prepared = prepareMutation(capabilityName, args, credentials.rootPath);
|
|
45409
46472
|
return withSftp(credentials, createClient, lookupHost, async (client) => {
|
|
45410
46473
|
let data4;
|
|
@@ -45438,7 +46501,7 @@ function createSftpConnector(options = {}) {
|
|
|
45438
46501
|
},
|
|
45439
46502
|
async test(source) {
|
|
45440
46503
|
try {
|
|
45441
|
-
const credentials =
|
|
46504
|
+
const credentials = readCredentials4(source);
|
|
45442
46505
|
await withSftp(credentials, createClient, lookupHost, (client) => client.cwd());
|
|
45443
46506
|
return { ok: true };
|
|
45444
46507
|
} catch (error) {
|
|
@@ -45506,7 +46569,7 @@ async function withSftp(credentials, createClient, lookupHost, run) {
|
|
|
45506
46569
|
await client.end().catch(() => false);
|
|
45507
46570
|
}
|
|
45508
46571
|
}
|
|
45509
|
-
function
|
|
46572
|
+
function readCredentials4(source) {
|
|
45510
46573
|
let raw;
|
|
45511
46574
|
if (source.credentials.kind === "custom") raw = source.credentials.values;
|
|
45512
46575
|
else if (source.credentials.kind === "api-key") {
|
|
@@ -55209,17 +56272,17 @@ var zohoBookingsConnector = {
|
|
|
55209
56272
|
category: "calendar",
|
|
55210
56273
|
defaultConsistencyModel: "authoritative",
|
|
55211
56274
|
capabilities: [
|
|
55212
|
-
|
|
55213
|
-
|
|
56275
|
+
readCapability8("workspaces.list", "List Zoho Bookings workspaces.", {}),
|
|
56276
|
+
readCapability8("services.list", "List services, optionally within one workspace.", {
|
|
55214
56277
|
workspace_id: { type: "string" }
|
|
55215
56278
|
}),
|
|
55216
|
-
|
|
56279
|
+
readCapability8("resources.list", "List bookable resources, optionally for one service.", {
|
|
55217
56280
|
service_id: { type: "string" }
|
|
55218
56281
|
}),
|
|
55219
|
-
|
|
56282
|
+
readCapability8("staff.list", "List staff, optionally for one service.", {
|
|
55220
56283
|
service_id: { type: "string" }
|
|
55221
56284
|
}),
|
|
55222
|
-
|
|
56285
|
+
readCapability8("appointments.list", "List appointments from a required start time.", {
|
|
55223
56286
|
from_time: {
|
|
55224
56287
|
type: "string",
|
|
55225
56288
|
description: "Zoho timestamp in dd-MMM-yyyy HH:mm:ss format."
|
|
@@ -55229,17 +56292,17 @@ var zohoBookingsConnector = {
|
|
|
55229
56292
|
staff_id: { type: "string" },
|
|
55230
56293
|
status: { type: "string" }
|
|
55231
56294
|
}, ["from_time"]),
|
|
55232
|
-
|
|
56295
|
+
readCapability8("appointments.get", "Read one appointment by booking id.", {
|
|
55233
56296
|
booking_id: { type: "string" }
|
|
55234
56297
|
}, ["booking_id"]),
|
|
55235
|
-
|
|
56298
|
+
readCapability8("availability.fetch", "Fetch open time slots for a service and date.", {
|
|
55236
56299
|
service_id: { type: "string" },
|
|
55237
56300
|
selected_date: { type: "string", description: "Date in YYYY-MM-DD format." },
|
|
55238
56301
|
staff_id: { type: "string" },
|
|
55239
56302
|
group_id: { type: "string" },
|
|
55240
56303
|
resource_id: { type: "string" }
|
|
55241
56304
|
}, ["service_id", "selected_date"]),
|
|
55242
|
-
|
|
56305
|
+
mutationCapability8("appointments.book", "Book an appointment for a customer.", {
|
|
55243
56306
|
service_id: { type: "string" },
|
|
55244
56307
|
from_time: { type: "string", description: "Zoho timestamp in dd-MMM-yyyy HH:mm:ss format." },
|
|
55245
56308
|
customer_details: {
|
|
@@ -55255,13 +56318,13 @@ var zohoBookingsConnector = {
|
|
|
55255
56318
|
additional_fields: { type: "object" },
|
|
55256
56319
|
payment_info: { type: "object" }
|
|
55257
56320
|
}, ["service_id", "from_time", "customer_details"]),
|
|
55258
|
-
|
|
56321
|
+
mutationCapability8("appointments.reschedule", "Move an appointment to a new time, staff member, or group.", {
|
|
55259
56322
|
booking_id: { type: "string" },
|
|
55260
56323
|
start_time: { type: "string", description: "Zoho timestamp in dd-MMM-yyyy HH:mm:ss format." },
|
|
55261
56324
|
staff_id: { type: "string" },
|
|
55262
56325
|
group_id: { type: "string" }
|
|
55263
56326
|
}, ["booking_id"]),
|
|
55264
|
-
|
|
56327
|
+
mutationCapability8("appointments.cancel", "Cancel an appointment by booking id.", {
|
|
55265
56328
|
booking_id: { type: "string" }
|
|
55266
56329
|
}, ["booking_id"])
|
|
55267
56330
|
]
|
|
@@ -55378,7 +56441,7 @@ var zohoBookingsConnector = {
|
|
|
55378
56441
|
}
|
|
55379
56442
|
}
|
|
55380
56443
|
};
|
|
55381
|
-
function
|
|
56444
|
+
function readCapability8(name, description, properties, required = []) {
|
|
55382
56445
|
return {
|
|
55383
56446
|
name,
|
|
55384
56447
|
class: "read",
|
|
@@ -55387,7 +56450,7 @@ function readCapability6(name, description, properties, required = []) {
|
|
|
55387
56450
|
requiredScopes: ["zohobookings.data.READ"]
|
|
55388
56451
|
};
|
|
55389
56452
|
}
|
|
55390
|
-
function
|
|
56453
|
+
function mutationCapability8(name, description, properties, required) {
|
|
55391
56454
|
return {
|
|
55392
56455
|
name,
|
|
55393
56456
|
class: "mutation",
|
|
@@ -62731,6 +63794,393 @@ var metabaseConnector = declarativeRestConnector({
|
|
|
62731
63794
|
]
|
|
62732
63795
|
});
|
|
62733
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
|
+
|
|
62734
64184
|
// src/connectors/adapters/segment.ts
|
|
62735
64185
|
var segmentConnector = declarativeRestConnector({
|
|
62736
64186
|
kind: "segment",
|
|
@@ -64226,6 +65676,7 @@ var CONNECTOR_ADAPTER_FACTORIES = [
|
|
|
64226
65676
|
defineFactoryAdapter(() => digitalOceanConnector, {}),
|
|
64227
65677
|
defineFactoryAdapter(() => clicksendConnector, {}),
|
|
64228
65678
|
defineFactoryAdapter(() => metabaseConnector, {}),
|
|
65679
|
+
defineFactoryAdapter(() => mysqlConnector, {}),
|
|
64229
65680
|
defineFactoryAdapter(
|
|
64230
65681
|
() => microsoftDynamics365BusinessCentralConnector,
|
|
64231
65682
|
microsoftOAuthEnvMap
|
|
@@ -64284,6 +65735,9 @@ var CONNECTOR_ADAPTER_FACTORIES = [
|
|
|
64284
65735
|
defineFactoryAdapter(() => httpConnector, {}),
|
|
64285
65736
|
defineFactoryAdapter(() => rssConnector, {}),
|
|
64286
65737
|
defineFactoryAdapter(() => kafkaConnector, {}),
|
|
65738
|
+
defineFactoryAdapter(() => duckdbConnector, {}),
|
|
65739
|
+
defineFactoryAdapter(() => rabbitMqConnector, {}),
|
|
65740
|
+
defineFactoryAdapter(() => redisConnector, {}),
|
|
64287
65741
|
defineFactoryAdapter(() => csvFilesConnector, {}),
|
|
64288
65742
|
defineFactoryAdapter(() => excelFilesConnector, {}),
|
|
64289
65743
|
defineFactoryAdapter(() => parquetFilesConnector, {}),
|
|
@@ -64356,10 +65810,7 @@ var CONNECTOR_ADAPTER_FACTORIES = [
|
|
|
64356
65810
|
clientId: "MONDAY_OAUTH_CLIENT_ID",
|
|
64357
65811
|
clientSecret: "MONDAY_OAUTH_CLIENT_SECRET"
|
|
64358
65812
|
}),
|
|
64359
|
-
defineFactoryAdapter(() => clickupConnector, {
|
|
64360
|
-
clientId: "CLICKUP_OAUTH_CLIENT_ID",
|
|
64361
|
-
clientSecret: "CLICKUP_OAUTH_CLIENT_SECRET"
|
|
64362
|
-
}),
|
|
65813
|
+
defineFactoryAdapter(() => clickupConnector, {}),
|
|
64363
65814
|
defineFactoryAdapter(() => basecampConnector, {
|
|
64364
65815
|
clientId: "BASECAMP_OAUTH_CLIENT_ID",
|
|
64365
65816
|
clientSecret: "BASECAMP_OAUTH_CLIENT_SECRET"
|
|
@@ -144139,6 +145590,7 @@ export {
|
|
|
144139
145590
|
deepgramConnector,
|
|
144140
145591
|
detectingAiConnector,
|
|
144141
145592
|
digitalPilotConnector,
|
|
145593
|
+
duckdbConnector,
|
|
144142
145594
|
docsbotConnector,
|
|
144143
145595
|
dripConnector,
|
|
144144
145596
|
echowinConnector,
|
|
@@ -144287,6 +145739,10 @@ export {
|
|
|
144287
145739
|
pipedriveConnector,
|
|
144288
145740
|
parquetFilesConnector,
|
|
144289
145741
|
quickbooksConnector,
|
|
145742
|
+
createRabbitMqConnector,
|
|
145743
|
+
rabbitMqConnector,
|
|
145744
|
+
createRedisConnector,
|
|
145745
|
+
redisConnector,
|
|
144290
145746
|
recallAiConnector,
|
|
144291
145747
|
rampConnector,
|
|
144292
145748
|
ringcentralConnector,
|
|
@@ -144354,6 +145810,8 @@ export {
|
|
|
144354
145810
|
googleBigqueryConnector,
|
|
144355
145811
|
hightouchConnector,
|
|
144356
145812
|
metabaseConnector,
|
|
145813
|
+
createMySqlConnector,
|
|
145814
|
+
mysqlConnector,
|
|
144357
145815
|
segmentConnector,
|
|
144358
145816
|
supabaseConnector,
|
|
144359
145817
|
builtwithConnector,
|
|
@@ -144745,4 +146203,4 @@ export {
|
|
|
144745
146203
|
demandbaseConnector,
|
|
144746
146204
|
adapters_exports
|
|
144747
146205
|
};
|
|
144748
|
-
//# sourceMappingURL=chunk-
|
|
146206
|
+
//# sourceMappingURL=chunk-FOIC32DF.js.map
|