@daeda/mcp-pro 0.1.16 → 0.1.17

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.
Files changed (2) hide show
  1. package/dist/index.js +149 -94
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2023,96 +2023,72 @@ var insertAssociationsViaStaging = (conn, rows) => pipe16(
2023
2023
  }).pipe(Effect23.catchAll(() => Effect23.void))
2024
2024
  )
2025
2025
  );
2026
- var makeDatabaseLive = (portalId, encryptionKey) => Layer2.scoped(
2026
+ var portalDbCache = /* @__PURE__ */ new Map();
2027
+ var initializePortalDb = async (portalId, encryptionKey) => {
2028
+ fs6.mkdirSync(portalDir(portalId), { recursive: true });
2029
+ if (encryptionKey) {
2030
+ const existingDb = dbPath(portalId);
2031
+ const walFile = existingDb + ".wal";
2032
+ if (fs6.existsSync(existingDb)) {
2033
+ const HEADER_SIZE = 24;
2034
+ const MAGIC_OFFSET = 8;
2035
+ const MAGIC_LENGTH = 4;
2036
+ const ENCRYPTION_FLAG_OFFSET = 20;
2037
+ const header = Buffer.alloc(HEADER_SIZE);
2038
+ const fd = fs6.openSync(existingDb, "r");
2039
+ try {
2040
+ fs6.readSync(fd, header, 0, HEADER_SIZE, 0);
2041
+ } finally {
2042
+ fs6.closeSync(fd);
2043
+ }
2044
+ const magic = header.toString("utf8", MAGIC_OFFSET, MAGIC_OFFSET + MAGIC_LENGTH);
2045
+ if (magic === "DUCK") {
2046
+ const isEncrypted = header[ENCRYPTION_FLAG_OFFSET] === 1;
2047
+ if (!isEncrypted) {
2048
+ console.error(`[encryption] Detected unencrypted DB for portal ${portalId} \u2014 deleting for encrypted re-sync`);
2049
+ fs6.unlinkSync(existingDb);
2050
+ if (fs6.existsSync(walFile)) fs6.unlinkSync(walFile);
2051
+ }
2052
+ }
2053
+ }
2054
+ }
2055
+ const instance = await DuckDBInstance3.create(":memory:");
2056
+ const conn = await instance.connect();
2057
+ const dbFile = dbPath(portalId).replace(/'/g, "''");
2058
+ const attachSql = encryptionKey ? `ATTACH '${dbFile}' AS portal_db (ENCRYPTION_KEY '${encryptionKey.replace(/'/g, "''")}')` : `ATTACH '${dbFile}' AS portal_db`;
2059
+ try {
2060
+ await conn.run(attachSql);
2061
+ } catch (e) {
2062
+ if (!isCorruptedDatabaseError(e)) throw e;
2063
+ const file = dbPath(portalId);
2064
+ console.error(`[recovery] Corrupted database detected during ATTACH for portal ${portalId}, deleting and retrying`);
2065
+ try {
2066
+ fs6.unlinkSync(file);
2067
+ } catch {
2068
+ }
2069
+ try {
2070
+ fs6.unlinkSync(file + ".wal");
2071
+ } catch {
2072
+ }
2073
+ await conn.run(attachSql);
2074
+ }
2075
+ await conn.run("USE portal_db");
2076
+ return { instance, conn };
2077
+ };
2078
+ var getOrCreatePortalDb = (portalId, encryptionKey) => {
2079
+ const cached = portalDbCache.get(portalId);
2080
+ if (cached) return cached;
2081
+ const creating = initializePortalDb(portalId, encryptionKey);
2082
+ portalDbCache.set(portalId, creating);
2083
+ creating.catch(() => portalDbCache.delete(portalId));
2084
+ return creating;
2085
+ };
2086
+ var makeDatabaseLive = (portalId, encryptionKey) => Layer2.effect(
2027
2087
  DatabaseService,
2028
2088
  pipe16(
2029
- Effect23.Do,
2030
- Effect23.tap(
2031
- () => Effect23.sync(() => {
2032
- fs6.mkdirSync(portalDir(portalId), { recursive: true });
2033
- if (encryptionKey) {
2034
- const existingDb = dbPath(portalId);
2035
- const walFile = existingDb + ".wal";
2036
- if (fs6.existsSync(existingDb)) {
2037
- const HEADER_SIZE = 24;
2038
- const MAGIC_OFFSET = 8;
2039
- const MAGIC_LENGTH = 4;
2040
- const ENCRYPTION_FLAG_OFFSET = 20;
2041
- const header = Buffer.alloc(HEADER_SIZE);
2042
- const fd = fs6.openSync(existingDb, "r");
2043
- try {
2044
- fs6.readSync(fd, header, 0, HEADER_SIZE, 0);
2045
- } finally {
2046
- fs6.closeSync(fd);
2047
- }
2048
- const magic = header.toString("utf8", MAGIC_OFFSET, MAGIC_OFFSET + MAGIC_LENGTH);
2049
- if (magic === "DUCK") {
2050
- const isEncrypted = header[ENCRYPTION_FLAG_OFFSET] === 1;
2051
- if (!isEncrypted) {
2052
- console.error(`[encryption] Detected unencrypted DB for portal ${portalId} \u2014 deleting for encrypted re-sync`);
2053
- fs6.unlinkSync(existingDb);
2054
- if (fs6.existsSync(walFile)) fs6.unlinkSync(walFile);
2055
- }
2056
- }
2057
- }
2058
- }
2059
- })
2060
- ),
2061
- Effect23.bind(
2062
- "instance",
2063
- () => Effect23.acquireRelease(
2064
- Effect23.tryPromise({
2065
- try: () => DuckDBInstance3.create(":memory:"),
2066
- catch: (e) => new DatabaseError({ message: "Failed to create DuckDB instance", cause: e })
2067
- }),
2068
- (inst) => Effect23.sync(() => inst.closeSync())
2069
- )
2070
- ),
2071
- Effect23.bind(
2072
- "conn",
2073
- ({ instance }) => Effect23.acquireRelease(
2074
- Effect23.tryPromise({
2075
- try: () => instance.connect(),
2076
- catch: (e) => new DatabaseError({ message: "Failed to connect to DuckDB", cause: e })
2077
- }),
2078
- (c) => Effect23.sync(() => c.closeSync())
2079
- )
2080
- ),
2081
- Effect23.tap(({ conn }) => {
2082
- const attachDb = Effect23.tryPromise({
2083
- try: async () => {
2084
- const dbFile = dbPath(portalId).replace(/'/g, "''");
2085
- if (encryptionKey) {
2086
- const escapedKey = encryptionKey.replace(/'/g, "''");
2087
- await conn.run(`ATTACH '${dbFile}' AS portal_db (ENCRYPTION_KEY '${escapedKey}')`);
2088
- } else {
2089
- await conn.run(`ATTACH '${dbFile}' AS portal_db`);
2090
- }
2091
- await conn.run(`USE portal_db`);
2092
- },
2093
- catch: (e) => new DatabaseError({ message: "Failed to attach database", cause: e })
2094
- });
2095
- return pipe16(
2096
- attachDb,
2097
- Effect23.catchAll((err) => {
2098
- if (!isCorruptedDatabaseError(err.cause ?? err)) return Effect23.fail(err);
2099
- return pipe16(
2100
- Effect23.sync(() => {
2101
- const file = dbPath(portalId);
2102
- console.error(`[recovery] Corrupted database detected during ATTACH for portal ${portalId}, deleting and retrying`);
2103
- try {
2104
- fs6.unlinkSync(file);
2105
- } catch {
2106
- }
2107
- try {
2108
- fs6.unlinkSync(file + ".wal");
2109
- } catch {
2110
- }
2111
- }),
2112
- Effect23.flatMap(() => attachDb)
2113
- );
2114
- })
2115
- );
2089
+ Effect23.tryPromise({
2090
+ try: () => getOrCreatePortalDb(portalId, encryptionKey),
2091
+ catch: (e) => new DatabaseError({ message: "Failed to acquire database connection", cause: e })
2116
2092
  }),
2117
2093
  Effect23.map(({ conn }) => {
2118
2094
  const pluginTableNames = new Set(
@@ -2381,9 +2357,19 @@ var makeDatabaseLive = (portalId, encryptionKey) => Layer2.scoped(
2381
2357
  throw new Error(`Column length mismatch for table ${tableName}`);
2382
2358
  }
2383
2359
  }
2384
- await conn.run(`DELETE FROM "${name}"`);
2385
- if (rowCount > 0) {
2386
- await bulkAppend(conn, name, columns);
2360
+ await conn.run("BEGIN TRANSACTION");
2361
+ try {
2362
+ await conn.run(`DELETE FROM "${name}"`);
2363
+ if (rowCount > 0) {
2364
+ await bulkAppend(conn, name, columns);
2365
+ }
2366
+ await conn.run("COMMIT");
2367
+ } catch (e) {
2368
+ try {
2369
+ await conn.run("ROLLBACK");
2370
+ } catch {
2371
+ }
2372
+ throw e;
2387
2373
  }
2388
2374
  return rowCount;
2389
2375
  },
@@ -4378,7 +4364,7 @@ var fields = [
4378
4364
  { name: "field_type", type: "string", required: true, description: "Field type: text, textarea, select, radio, checkbox, number, date, booleancheckbox" },
4379
4365
  { name: "group_name", type: "string", required: true, description: "Property group (e.g. 'contactinformation')" },
4380
4366
  { name: "property_description", type: "string", required: false, description: "Property description shown in HubSpot" },
4381
- { name: "options", type: "array", required: false, description: "Options for enumeration types", itemShape: "{ label: string, value: string, display_order: number }" },
4367
+ { name: "options", type: "array", required: false, description: "Options for enumeration types (boolean options are injected automatically \u2014 do not provide for bool/booleancheckbox)", itemShape: "{ label: string, value: string, display_order: number }" },
4382
4368
  { name: "conditional_options", type: "array", required: false, description: "Conditional option rules \u2014 limit visible options based on another property's value. Creates a follow-up HubSpot task (requires Professional+)", itemShape: "{ controlling_property: string, controlling_value: string, visible_options: string[] }" }
4383
4369
  ];
4384
4370
  var createPropertyMeta = {
@@ -4386,7 +4372,7 @@ var createPropertyMeta = {
4386
4372
  category: "create",
4387
4373
  executionMode: "hubspot",
4388
4374
  summary: "Create a custom property on a CRM object type",
4389
- description: "Add a new custom property/field to an object type (e.g. 'add a dropdown for lead scoring to contacts'). For enumeration types, include options. For deals, each stage requires metadata.probability.",
4375
+ description: "Add a new custom property/field to an object type (e.g. 'add a dropdown for lead scoring to contacts'). For enumeration types, include options. For bool/booleancheckbox types, options are handled automatically \u2014 do not provide them. For deals, each stage requires metadata.probability.",
4390
4376
  fields,
4391
4377
  example: {
4392
4378
  fields: {
@@ -6948,6 +6934,10 @@ Note: Conditional options require a HubSpot Professional or Enterprise subscript
6948
6934
  };
6949
6935
 
6950
6936
  // ../shared/operations/create-property/index.ts
6937
+ var BOOLEAN_OPTIONS = [
6938
+ { label: "Yes", value: "true", displayOrder: 0 },
6939
+ { label: "No", value: "false", displayOrder: 1 }
6940
+ ];
6951
6941
  var VALID_PROPERTY_TYPES = /* @__PURE__ */ new Set([
6952
6942
  "string",
6953
6943
  "number",
@@ -7139,7 +7129,7 @@ var createPropertyHandler = {
7139
7129
  fieldType: op.property.field_type,
7140
7130
  groupName: op.property.group_name,
7141
7131
  description: op.property.description,
7142
- options: op.property.options?.map((o) => ({
7132
+ options: op.property.type === "bool" ? BOOLEAN_OPTIONS : op.property.options?.map((o) => ({
7143
7133
  label: o.label,
7144
7134
  value: o.value,
7145
7135
  displayOrder: o.display_order
@@ -12034,6 +12024,16 @@ No plans found${filterMsg}.` }]
12034
12024
  });
12035
12025
  }
12036
12026
 
12027
+ // src/pure/plugin-sync-gate.ts
12028
+ var PLUGIN_SYNC_DEBOUNCE_MS = 30 * 60 * 1e3;
12029
+ var LAST_PLUGIN_SYNC_ALL_KEY = "last_plugin_sync_all";
12030
+ var shouldRunPluginSync = (lastSyncAllRaw) => {
12031
+ if (!lastSyncAllRaw) return true;
12032
+ const parsed = Date.parse(lastSyncAllRaw);
12033
+ if (Number.isNaN(parsed)) return true;
12034
+ return Date.now() - parsed >= PLUGIN_SYNC_DEBOUNCE_MS;
12035
+ };
12036
+
12037
12037
  // src/pure/sync-lifecycle.ts
12038
12038
  var dedupePortalIds2 = (portalIds) => {
12039
12039
  const seen = /* @__PURE__ */ new Set();
@@ -12350,6 +12350,58 @@ var mainProgram = Effect94.gen(function* () {
12350
12350
  Layer6.provide(Layer6.mergeAll(wsLayer, dbLayer, configLayer, portalStateLayer))
12351
12351
  );
12352
12352
  };
12353
+ const triggerPluginSync = (portalId) => {
12354
+ const syncLayer = makeSyncLayer(portalId);
12355
+ const dbLayer = makeDatabaseLive(portalId, getEncryptionKey());
12356
+ Effect94.runFork(
12357
+ pipe79(
12358
+ Effect94.Do,
12359
+ Effect94.bind("db", () => pipe79(DatabaseService, Effect94.provide(dbLayer))),
12360
+ Effect94.bind("lastSyncAll", ({ db }) => db.getMetadata(LAST_PLUGIN_SYNC_ALL_KEY)),
12361
+ Effect94.tap(({ lastSyncAll }) => {
12362
+ if (!shouldRunPluginSync(lastSyncAll)) {
12363
+ return Effect94.sync(
12364
+ () => console.error(`[plugin-sync] Skipping plugin sync for portal ${portalId} (debounce \u2014 last sync: ${lastSyncAll})`)
12365
+ );
12366
+ }
12367
+ return pipe79(
12368
+ portalState.resetStuckPlugins(portalId),
12369
+ Effect94.catchAll(() => Effect94.void),
12370
+ Effect94.tap(() => Effect94.sync(
12371
+ () => console.error(`[plugin-sync] Starting message plugin sync for portal ${portalId}`)
12372
+ )),
12373
+ Effect94.flatMap(
12374
+ () => pipe79(
12375
+ SyncService,
12376
+ Effect94.flatMap((sync) => sync.syncMessagePlugins(portalId)),
12377
+ Effect94.provide(syncLayer)
12378
+ )
12379
+ ),
12380
+ Effect94.flatMap(
12381
+ () => pipe79(
12382
+ DatabaseService,
12383
+ Effect94.flatMap((db) => db.setMetadata(LAST_PLUGIN_SYNC_ALL_KEY, (/* @__PURE__ */ new Date()).toISOString())),
12384
+ Effect94.provide(dbLayer)
12385
+ )
12386
+ ),
12387
+ Effect94.tap(() => Effect94.sync(
12388
+ () => console.error(`[plugin-sync] Completed message plugin sync for portal ${portalId}`)
12389
+ )),
12390
+ Effect94.catchAll(
12391
+ (error) => Effect94.sync(
12392
+ () => console.error(`[plugin-sync] Failed for portal ${portalId}:`, error)
12393
+ )
12394
+ )
12395
+ );
12396
+ }),
12397
+ Effect94.catchAll(
12398
+ (error) => Effect94.sync(
12399
+ () => console.error(`[plugin-sync] Failed to check debounce for portal ${portalId}:`, error)
12400
+ )
12401
+ )
12402
+ )
12403
+ );
12404
+ };
12353
12405
  configureArtifactQueue(
12354
12406
  (portalId, artifact) => pipe79(
12355
12407
  SyncService,
@@ -12486,6 +12538,9 @@ var mainProgram = Effect94.gen(function* () {
12486
12538
  for (const portalId of warmupPortalIds) {
12487
12539
  Effect94.runFork(pipe79(ws.sendSyncFull(portalId), Effect94.catchAll(() => Effect94.void)));
12488
12540
  }
12541
+ for (const portalId of warmupPortalIds) {
12542
+ triggerPluginSync(portalId);
12543
+ }
12489
12544
  },
12490
12545
  onSyncDiffNeeded: (portalId) => {
12491
12546
  const selectedPortalId2 = getSelectedPortalId(config);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daeda/mcp-pro",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "MCP server for HubSpot CRM — sync, query, and manage your portal data",
5
5
  "type": "module",
6
6
  "bin": {