@friggframework/core 2.0.0--canary.698.b1a2d30.0 → 2.0.0--canary.699.0d57ee8.0

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 (92) hide show
  1. package/generated/prisma-mongodb/edge.js +2 -2
  2. package/generated/prisma-mongodb/index.js +2 -2
  3. package/generated/prisma-mongodb/package.json +1 -1
  4. package/generated/prisma-mongodb/schema.prisma +2 -2
  5. package/generated/prisma-mongodb/wasm.js +2 -2
  6. package/generated/prisma-postgresql/edge.js +2 -2
  7. package/generated/prisma-postgresql/index.js +2 -2
  8. package/generated/prisma-postgresql/package.json +1 -1
  9. package/generated/prisma-postgresql/schema.prisma +2 -2
  10. package/generated/prisma-postgresql/wasm.js +2 -2
  11. package/handlers/app-handler-helpers.js +5 -2
  12. package/handlers/integration-event-dispatcher.js +1 -1
  13. package/integrations/{data-defined → definitions}/README.md +12 -12
  14. package/integrations/{data-defined → definitions}/capabilities/AUDIT.md +12 -11
  15. package/integrations/{data-defined → definitions}/capabilities/asana.capabilities.json +1 -2
  16. package/integrations/{data-defined → definitions}/capabilities/fathom.capabilities.json +1 -2
  17. package/integrations/{data-defined → definitions}/capabilities/hubspot.capabilities.json +1 -2
  18. package/integrations/{data-defined → definitions}/capabilities/linear.capabilities.json +1 -2
  19. package/integrations/{data-defined → definitions}/capabilities/phone.capabilities.json +1 -2
  20. package/integrations/{data-defined → definitions}/capabilities/pipedrive.capabilities.json +1 -2
  21. package/integrations/{data-defined → definitions}/capabilities/salesforce.capabilities.json +1 -1
  22. package/integrations/{data-defined → definitions}/capabilities/slack.capabilities.json +1 -2
  23. package/integrations/{data-defined → definitions}/capabilities/stripe.capabilities.json +1 -2
  24. package/integrations/{data-defined → definitions}/config-form.js +1 -1
  25. package/integrations/{data-defined → definitions}/examples/pipedrive-hubspot.definition.json +1 -1
  26. package/integrations/{data-defined → definitions}/examples/records/phone-hubspot.integration.json +1 -1
  27. package/integrations/{data-defined → definitions}/expressions.js +1 -1
  28. package/integrations/{data-defined → definitions}/index.js +2 -2
  29. package/integrations/{data-defined/data-defined-integration.js → definitions/integration-runtime.js} +22 -21
  30. package/integrations/{data-defined → definitions}/renderers/hubspot.js +13 -8
  31. package/integrations/{data-defined → definitions}/router.js +11 -11
  32. package/integrations/{data-defined → definitions}/runtime.js +1 -1
  33. package/integrations/{data-defined → definitions}/schema/installation.v0.1.schema.json +1 -1
  34. package/integrations/{data-defined → definitions}/schema/records.md +1 -1
  35. package/integrations/{data-defined → definitions}/scripts/validate.js +1 -1
  36. package/integrations/{data-defined → definitions}/use-cases/definition-use-cases.js +1 -1
  37. package/integrations/{data-defined → definitions}/versioning.js +1 -1
  38. package/integrations/integration-base.js +31 -1
  39. package/integrations/integration-router.js +1 -1
  40. package/package.json +6 -6
  41. package/prisma-mongodb/schema.prisma +2 -2
  42. package/prisma-postgresql/schema.prisma +2 -2
  43. /package/integrations/{data-defined → definitions}/canonical.js +0 -0
  44. /package/integrations/{data-defined → definitions}/capabilities/acmeerp.capabilities.json +0 -0
  45. /package/integrations/{data-defined → definitions}/capabilities/calendly.capabilities.json +0 -0
  46. /package/integrations/{data-defined → definitions}/capabilities/docusign.capabilities.json +0 -0
  47. /package/integrations/{data-defined → definitions}/capabilities/github.capabilities.json +0 -0
  48. /package/integrations/{data-defined → definitions}/capabilities/jira.capabilities.json +0 -0
  49. /package/integrations/{data-defined → definitions}/capabilities/klaviyo.capabilities.json +0 -0
  50. /package/integrations/{data-defined → definitions}/capabilities/netsuite.capabilities.json +0 -0
  51. /package/integrations/{data-defined → definitions}/capabilities/notion.capabilities.json +0 -0
  52. /package/integrations/{data-defined → definitions}/capabilities/quickbooks.capabilities.json +0 -0
  53. /package/integrations/{data-defined → definitions}/capabilities/shopify.capabilities.json +0 -0
  54. /package/integrations/{data-defined → definitions}/capabilities/webflow.capabilities.json +0 -0
  55. /package/integrations/{data-defined → definitions}/capabilities/zendesk.capabilities.json +0 -0
  56. /package/integrations/{data-defined → definitions}/compile.js +0 -0
  57. /package/integrations/{data-defined → definitions}/examples/acme-erp-quickbooks.definition.json +0 -0
  58. /package/integrations/{data-defined → definitions}/examples/asana-slack.definition.json +0 -0
  59. /package/integrations/{data-defined → definitions}/examples/calendly-salesforce.definition.json +0 -0
  60. /package/integrations/{data-defined → definitions}/examples/docusign-salesforce.definition.json +0 -0
  61. /package/integrations/{data-defined → definitions}/examples/fathom-hubspot-notion.definition.json +0 -0
  62. /package/integrations/{data-defined → definitions}/examples/github-linear.definition.json +0 -0
  63. /package/integrations/{data-defined → definitions}/examples/hubspot-slack.definition.json +0 -0
  64. /package/integrations/{data-defined → definitions}/examples/klaviyo-hubspot.definition.json +0 -0
  65. /package/integrations/{data-defined → definitions}/examples/phone-hubspot.definition.json +0 -0
  66. /package/integrations/{data-defined → definitions}/examples/pipedrive-phone.definition.json +0 -0
  67. /package/integrations/{data-defined → definitions}/examples/records/phone-hubspot.definition-version.json +0 -0
  68. /package/integrations/{data-defined → definitions}/examples/records/phone-hubspot.mappings.json +0 -0
  69. /package/integrations/{data-defined → definitions}/examples/records/phone-hubspot.workflow-run.json +0 -0
  70. /package/integrations/{data-defined → definitions}/examples/shopify-klaviyo.definition.json +0 -0
  71. /package/integrations/{data-defined → definitions}/examples/shopify-netsuite.definition.json +0 -0
  72. /package/integrations/{data-defined → definitions}/examples/stripe-quickbooks.definition.json +0 -0
  73. /package/integrations/{data-defined → definitions}/examples/webflow-hubspot-slack.definition.json +0 -0
  74. /package/integrations/{data-defined → definitions}/examples/zendesk-jira.definition.json +0 -0
  75. /package/integrations/{data-defined → definitions}/interpreter.js +0 -0
  76. /package/integrations/{data-defined → definitions}/manifests.js +0 -0
  77. /package/integrations/{data-defined → definitions}/renderers/common.js +0 -0
  78. /package/integrations/{data-defined → definitions}/renderers/html.js +0 -0
  79. /package/integrations/{data-defined → definitions}/renderers/preview.js +0 -0
  80. /package/integrations/{data-defined → definitions}/renderers/slack.js +0 -0
  81. /package/integrations/{data-defined → definitions}/repositories/definition-version-repository.js +0 -0
  82. /package/integrations/{data-defined → definitions}/repositories/workflow-run-repository.js +0 -0
  83. /package/integrations/{data-defined → definitions}/schema/capability-manifest.v0.1.schema.json +0 -0
  84. /package/integrations/{data-defined → definitions}/schema/integration-definition.v0.1.schema.json +0 -0
  85. /package/integrations/{data-defined → definitions}/schema-check.js +0 -0
  86. /package/integrations/{data-defined → definitions}/scripts/check-manifests.js +0 -0
  87. /package/integrations/{data-defined → definitions}/steps.js +0 -0
  88. /package/integrations/{data-defined → definitions}/sync-engine.js +0 -0
  89. /package/integrations/{data-defined → definitions}/tests/helpers-fake-crm.js +0 -0
  90. /package/integrations/{data-defined → definitions}/transforms.js +0 -0
  91. /package/integrations/{data-defined → definitions}/validate.js +0 -0
  92. /package/integrations/{data-defined → definitions}/webhooks.js +0 -0
@@ -1,10 +1,10 @@
1
1
  /**
2
- * DataDefinedIntegration: one IntegrationBase subclass that runs any published definition.
2
+ * IntegrationRuntime: one IntegrationBase subclass that runs any published definition.
3
3
  *
4
- * Frigg registers integration classes at startup (appDefinition.integrations). For data-defined integrations the app
4
+ * Frigg registers integration classes at startup (appDefinition.integrations). For integrations published as definitions the app
5
5
  * registers one generated class per definition id:
6
6
  *
7
- * const { defineIntegrationClass } = require('@friggframework/core/integrations/data-defined');
7
+ * const { defineIntegrationClass } = require('@friggframework/core/integrations/definitions');
8
8
  * integrations: [ defineIntegrationClass({ id: 'phone-hubspot', definitions, manifests, modules: { phone: PhoneModule, hubspot: HubSpotModule } }) ]
9
9
  *
10
10
  * The class's static Definition (name, modules) is what the infrastructure needs, and it is the same for every
@@ -23,7 +23,7 @@ const { createSyncEngine } = require('./sync-engine');
23
23
  const { eventsIn, handshakeFor, modulesFor } = require('./webhooks');
24
24
  const { buildConfigForm, missingRequired } = require('./config-form');
25
25
 
26
- /** The tick every data-defined trigger runs on: a chosen interval is rounded up to whole multiples of it. */
26
+ /** The tick every trigger of a published definition runs on: a chosen interval is rounded up to whole multiples of it. */
27
27
  const INSTANCE_TICK = '5m';
28
28
 
29
29
  /** A compiled definition's triggers as IntegrationBase trigger declarations: each runs its compiled event. */
@@ -44,7 +44,7 @@ function triggersOf(def, config) {
44
44
  * definition. Publishing a new VERSION deploys nothing; a new definition ID is a new class (a deploy, and its own
45
45
  * functions and queue). Right for a product team with a handful of integrations.
46
46
  *
47
- * Shape B — one class for every definition (defineSharedIntegrationClass): registered once as "data-defined", its
47
+ * Shape B — one class for every definition (defineSharedIntegrationClass): registered once as "integration-runtime", its
48
48
  * static modules are every API module the app has installed, and each installation names its definition id and
49
49
  * version in config. A new integration is a row, not a deploy, and every installation shares one set of functions
50
50
  * and one queue. Right for a multi-tenant product, where definitions accumulate without bound.
@@ -60,11 +60,11 @@ function defineIntegrationClass({ id, definitions, manifests, modules, runCode,
60
60
  });
61
61
  }
62
62
 
63
- function defineSharedIntegrationClass({ name = 'data-defined', definitions, manifests, modules, runCode, IntegrationBase, lookup, runs }) {
63
+ function defineSharedIntegrationClass({ name = 'integration-runtime', definitions, manifests, modules, runCode, IntegrationBase, lookup, runs }) {
64
64
  return makeClass({
65
65
  name, defaultId: null, definitions, manifests, runCode, IntegrationBase, lookup, runs,
66
66
  modules: Object.fromEntries(Object.entries(modules).map(([key, definition]) => [key, { definition }])),
67
- display: { label: 'Data-defined integration', description: 'Runs any published integration definition.', category: 'Custom' },
67
+ display: { label: 'Integration runtime', description: 'Runs any published integration definition.', category: 'Custom' },
68
68
  version: '0.1.0',
69
69
  });
70
70
  }
@@ -132,7 +132,7 @@ function makeClass({ name, defaultId, definitions, manifests, runCode, Integrati
132
132
  // database can pass their own store or `false` to keep none).
133
133
  let runStore = runs;
134
134
  // App-level webhooks fan out to the installations of every type this deployment runs.
135
- const findInstallations = (externalId, moduleName, Cls) => (lookup || friggLookup(async (type) => type === id || type === Cls.Definition.name || Boolean(await DataDefinedIntegration.IntegrationProvider?.resolve(type))))(externalId, moduleName);
135
+ const findInstallations = (externalId, moduleName, Cls) => (lookup || friggLookup(async (type) => type === id || type === Cls.Definition.name || Boolean(await IntegrationRuntime.IntegrationProvider?.resolve(type))))(externalId, moduleName);
136
136
  const runRepository = () => (runStore === undefined ? (runStore = require('./repositories/workflow-run-repository').createWorkflowRunRepository()) : runStore);
137
137
  const baseFor = (typeName) => `${process.env.FRIGG_PUBLIC_URL || ''}/api/${typeName}-integration/webhooks`;
138
138
  const pinnedDefinitions = new Map(); // `${id}@${version}` -> definition, for the trigger tick
@@ -142,7 +142,7 @@ function makeClass({ name, defaultId, definitions, manifests, runCode, Integrati
142
142
  // A code class's own Definition.triggers (IntegrationBase.triggersFor), kept beside the compiled ones.
143
143
  const codeTriggers = async (Cls, installation) => (embedded && typeof Base.triggersFor === 'function' ? Base.triggersFor.call(Cls, installation) : []);
144
144
 
145
- class DataDefinedIntegration extends Base {
145
+ class IntegrationRuntime extends Base {
146
146
  // version is display only; installations pin their own. A code class keeps every part of its Definition.
147
147
  // instanceTriggers: schedule-triggered workflows and polling syncs are this class's recurring triggers,
148
148
  // resolved per installation (triggersFor) on one 5m tick.
@@ -190,8 +190,8 @@ function makeClass({ name, defaultId, definitions, manifests, runCode, Integrati
190
190
  return [...new Set([own, ...provided])];
191
191
  }
192
192
 
193
- /** How the routers find data-defined classes and the store and manifests they run on. */
194
- static DataDefined = { definitions, manifests, shared: !defaultId, runs: () => runRepository() };
193
+ /** How the routers find integration runtime classes and the store and manifests they run on. */
194
+ static IntegrationRuntime = { definitions, manifests, shared: !defaultId, runs: () => runRepository() };
195
195
 
196
196
  /** Shape A: the one definition this class runs. A class generated for a published definition sets its own. */
197
197
  static definitionId = defaultId;
@@ -315,7 +315,7 @@ function makeClass({ name, defaultId, definitions, manifests, runCode, Integrati
315
315
  if (embedded && !ownsWebhooks(this.def)) return super.onWebhook(params);
316
316
  const { data = {} } = params || {};
317
317
  if (data.module && data.topic) return this.routeEvents(data.module, [{ topic: data.topic, event: data.body, eventId: data.eventId || data.body?.id || null }]);
318
- const delivery = { body: data.body, headers: data.headers || {}, query: data.query || {} };
318
+ const delivery = { body: data.body, headers: data.headers || {}, query: data.query || {}, rawBody: data.rawBody, method: data.method, url: data.url };
319
319
  // No installation yet (an app-level webhook): find the ones it is for.
320
320
  if (!this.id || !this.def) return this.fanOutAppWebhook(delivery);
321
321
 
@@ -325,7 +325,8 @@ function makeClass({ name, defaultId, definitions, manifests, runCode, Integrati
325
325
  const manifest = manifests[this.def.modules[key].module];
326
326
  if (manifest.webhook?.verify) {
327
327
  let ok = false;
328
- try { ok = await this.moduleFor(key).api[manifest.webhook.verify]({ headers: delivery.headers, body: delivery.body, query: delivery.query }); } catch { ok = false; }
328
+ // The raw body, method and URL travel from receipt (IntegrationBase.onWebhookReceived): vendors sign bytes.
329
+ try { ok = await this.moduleFor(key).api[manifest.webhook.verify]({ headers: delivery.headers, body: delivery.body, query: delivery.query, rawBody: delivery.rawBody, method: delivery.method, url: delivery.url }); } catch { ok = false; }
329
330
  if (ok !== true) { this.logWebhook('webhook.rejected', { module: key }); return { routed: 0, rejected: true }; }
330
331
  }
331
332
  const own = this.accountOf(key);
@@ -352,7 +353,7 @@ function makeClass({ name, defaultId, definitions, manifests, runCode, Integrati
352
353
  let queued = 0;
353
354
  for (const account of accounts) {
354
355
  for (const integrationId of await findInstallations(account, moduleName, this.constructor)) {
355
- await this.queueWebhook({ integrationId, body: delivery.body, headers: delivery.headers, query: delivery.query });
356
+ await this.queueWebhook({ integrationId, body: delivery.body, headers: delivery.headers, query: delivery.query, rawBody: delivery.rawBody, method: delivery.method, url: delivery.url });
356
357
  queued += 1;
357
358
  }
358
359
  }
@@ -366,7 +367,7 @@ function makeClass({ name, defaultId, definitions, manifests, runCode, Integrati
366
367
  }
367
368
 
368
369
  logWebhook(eventName, fields) {
369
- try { require('../../logs').getLogger('frigg.data-defined').info('Webhook not routed', { eventName: `frigg.data-defined.${eventName}`, integrationId: this.id || null, ...fields }); } catch { /* logging must never break delivery */ }
370
+ try { require('../../logs').getLogger('frigg.integrations.runtime').info('Webhook not routed', { eventName: `frigg.integrations.runtime.${eventName}`, integrationId: this.id || null, ...fields }); } catch { /* logging must never break delivery */ }
370
371
  }
371
372
 
372
373
  async onCreate(params) {
@@ -419,7 +420,7 @@ function makeClass({ name, defaultId, definitions, manifests, runCode, Integrati
419
420
  const store = runRepository();
420
421
  if (store) await store.createRun({ integrationId: this.id, definitionId: name, definitionVersion: this.def?.version, workflowId: event, triggerType: 'code', status, error: error ? String(error.message || error) : null, retryable: Boolean(error), trace: [], startedAt, endedAt: new Date().toISOString() });
421
422
  } catch (e) {
422
- try { require('../../logs').getLogger('frigg.data-defined').warn('Workflow run not recorded', { eventName: 'frigg.data-defined.run.not_recorded', integrationId: this.id, workflowId: event, cause: e?.message }); } catch { /* ignore */ }
423
+ try { require('../../logs').getLogger('frigg.integrations.runtime').warn('Workflow run not recorded', { eventName: 'frigg.integrations.runtime.run.not_recorded', integrationId: this.id, workflowId: event, cause: e?.message }); } catch { /* ignore */ }
423
424
  }
424
425
  };
425
426
  let out;
@@ -527,7 +528,7 @@ function makeClass({ name, defaultId, definitions, manifests, runCode, Integrati
527
528
  if (store && rowId && store.updateRun) row = await store.updateRun(rowId, { status: run.status, reason: run.reason, error: run.error, retryable: Boolean(run.retryable), trace: run.trace, waitingFor: run.waitingFor ?? null, resumeAt: run.resumeAt ?? null, endedAt: run.endedAt ?? null });
528
529
  else if (store) row = await store.createRun({ ...stored, runKey: run.id, integrationId: this.id, definitionId: run.definition?.id, definitionVersion: run.definition?.version, triggerType: run.trigger?.type, eventId: run.trigger?.eventId });
529
530
  } catch (e) {
530
- try { require('../../logs').getLogger('frigg.data-defined').warn('Workflow run not recorded', { eventName: 'frigg.data-defined.run.not_recorded', integrationId: this.id, workflowId: run.workflowId, cause: e?.message }); } catch { /* ignore */ }
531
+ try { require('../../logs').getLogger('frigg.integrations.runtime').warn('Workflow run not recorded', { eventName: 'frigg.integrations.runtime.run.not_recorded', integrationId: this.id, workflowId: run.workflowId, cause: e?.message }); } catch { /* ignore */ }
531
532
  }
532
533
  if (run.status === 'waiting' && row?.id && !rowId) await this.mappingStore().put(`wait:${run.id}`, { ...(await this.mappingStore().get(`wait:${run.id}`)), rowId: row.id });
533
534
  if (run.status === 'failed') { const e = new Error(run.error); e.run = run; throw e; } // the queue retries
@@ -560,7 +561,7 @@ function makeClass({ name, defaultId, definitions, manifests, runCode, Integrati
560
561
  queueUrl: typeof this.scheduleQueueUrl === 'function' ? this.scheduleQueueUrl() : process.env[`${name.toUpperCase().replace(/-/g, '_')}_QUEUE_URL`],
561
562
  });
562
563
  if (booked?.error || booked?.warning) {
563
- try { require('../../logs').getLogger('frigg.data-defined').error('Run resume not booked', { eventName: 'frigg.data-defined.run.resume_not_booked', integrationId: this.id, runId: run.id, kind: cp.wait.kind, reason: booked.reason || booked.warning }); } catch { /* ignore */ }
564
+ try { require('../../logs').getLogger('frigg.integrations.runtime').error('Run resume not booked', { eventName: 'frigg.integrations.runtime.run.resume_not_booked', integrationId: this.id, runId: run.id, kind: cp.wait.kind, reason: booked.reason || booked.warning }); } catch { /* ignore */ }
564
565
  }
565
566
  }
566
567
 
@@ -678,9 +679,9 @@ function makeClass({ name, defaultId, definitions, manifests, runCode, Integrati
678
679
  return out;
679
680
  }
680
681
  }
681
- Object.defineProperty(DataDefinedIntegration, 'name', { value: `DataDefined_${id.replace(/-/g, '_')}` });
682
- if (!defaultId) DataDefinedIntegration.IntegrationProvider = definitionProvider(DataDefinedIntegration, definitions);
683
- return DataDefinedIntegration;
682
+ Object.defineProperty(IntegrationRuntime, 'name', { value: `IntegrationRuntime_${id.replace(/-/g, '_')}` });
683
+ if (!defaultId) IntegrationRuntime.IntegrationProvider = definitionProvider(IntegrationRuntime, definitions);
684
+ return IntegrationRuntime;
684
685
  }
685
686
 
686
687
  /**
@@ -1,9 +1,14 @@
1
1
  /**
2
- * Resolved view tree -> a JSON description of HubSpot UI-extension components (`@hubspot/ui-extensions` names). Data,
3
- * not React: a thin card deployed once maps `{ type, props, children }` onto `React.createElement(components[type], ...)`
4
- * and routes `props.action` to the actions endpoint, so a changed card is a publish, not a card deploy.
2
+ * Resolved view tree -> a JSON description of HubSpot UI-extension components. Names and prop values were checked
3
+ * against @hubspot/ui-extensions 0.17.1 (its shared.synced/types/components/*.d.ts). Data, not React: a thin card
4
+ * deployed once maps `{ type, props, children }` onto `React.createElement(components[type], props, ...children)`, so a
5
+ * changed card is a publish, not a card deploy.
5
6
  *
6
- * A node is `{ type, props?, children? }`; a child is a string (text) or a node. Strings are text, never markup.
7
+ * A node is `{ type, props?, children?, action? }`; a child is a string (text) or a node. Strings are text, never markup.
8
+ * `props` holds only real component props, passed straight through. `action` is OURS, not a HubSpot prop: the card must
9
+ * not spread it onto the component, but turn it into `onClick={() => runServerless(...)}` that posts it to the actions
10
+ * endpoint (a userAction button), because a UI extension cannot call out except through a serverless function.
11
+ * An `open_url` button needs no action: it is the real `href` prop, which the host opens in a new tab.
7
12
  */
8
13
  const { str, safeUrl, toneOf } = require('./common');
9
14
 
@@ -11,7 +16,7 @@ const { str, safeUrl, toneOf } = require('./common');
11
16
  const HUBSPOT_COMPONENTS = ['Flex', 'Heading', 'Text', 'List', 'Tag', 'Button', 'EmptyState', 'DescriptionList', 'DescriptionListItem', 'Divider', 'Table', 'TableHead', 'TableBody', 'TableRow', 'TableHeader', 'TableCell'];
12
17
  const TAG_VARIANT = { positive: 'success', warning: 'warning', critical: 'error', neutral: 'default' };
13
18
 
14
- const node = (type, props, children) => ({ type, ...(props && Object.keys(props).length ? { props } : {}), ...(children && children.length ? { children } : {}) });
19
+ const node = (type, props, children, action) => ({ type, ...(props && Object.keys(props).length ? { props } : {}), ...(action ? { action } : {}), ...(children && children.length ? { children } : {}) });
15
20
 
16
21
  /**
17
22
  * @param {object|null} view
@@ -34,16 +39,16 @@ function viewToHubSpot(view, { integrationId, surface } = {}) {
34
39
  node('TableBody', {}, (Array.isArray(p.rows) ? p.rows : []).map((r) => node('TableRow', {}, (Array.isArray(r) ? r : []).map((c) => node('TableCell', {}, [str(c)]))))),
35
40
  ]);
36
41
  case 'Badge': return node('Tag', { variant: TAG_VARIANT[toneOf(p.tone)] }, [str(p.text)]);
37
- case 'Empty': return node('EmptyState', { title: str(p.text), layout: 'vertical' });
42
+ case 'Empty': return node('EmptyState', { layout: 'vertical' }, [node('Text', {}, [str(p.text)])]);
38
43
  case 'Divider': return node('Divider', { distance: 'sm' });
39
44
  case 'Button': {
40
45
  const a = p.action;
41
46
  const variant = p.style === 'primary' ? 'primary' : p.style === 'danger' ? 'destructive' : 'secondary';
42
47
  let action;
43
48
  if (a?.type === 'userAction' && typeof a.workflow === 'string') action = { type: 'userAction', workflow: a.workflow, surface, ...(integrationId ? { integrationId: String(integrationId) } : {}), ...(a.input ? { input: a.input } : {}) };
44
- else if (a?.type === 'open_url' && safeUrl(a.url)) action = { type: 'open_url', url: safeUrl(a.url) };
49
+ const href = a?.type === 'open_url' ? safeUrl(a.url) : null;
45
50
  // A button with no usable action still renders (disabled), so the card matches the definition.
46
- return node('Button', { variant, ...(action ? { action } : { disabled: true }) }, [str(p.label) || 'Open']);
51
+ return node('Button', { variant, ...(href ? { href } : action ? {} : { disabled: true }) }, [str(p.label) || 'Open'], action);
47
52
  }
48
53
  default: return kids().length === 1 ? kids()[0] : node('Flex', { direction: 'column', gap: 'sm' }, kids());
49
54
  }
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * HTTP routes for integration versions, run history, sync progress and approvals, mounted by createIntegrationRouter
3
- * when the app registers the integration runtime (a class with `DataDefined`). They answer for EVERY integration,
3
+ * when the app registers the integration runtime (a class with `IntegrationRuntime`). They answer for EVERY integration,
4
4
  * however it was made: an integration with no run history, syncs or other versions gets an empty answer, not a 404.
5
5
  * Thin adapters: authenticate, call a use case, map the result to a status.
6
6
  *
7
7
  * Signed-in users (Frigg's usual authentication: bearer token, x-frigg headers or adopter JWT):
8
- * GET /api/data-defined/definitions what can be installed: latest published version of each
8
+ * GET /api/integration-definitions what can be installed: latest published version of each
9
9
  * (GET /api/integrations lists the same types as options)
10
10
  * GET /api/integrations/:integrationId/upgrade?to= the upgrade plan for an installation (changes nothing)
11
11
  * POST /api/integrations/:integrationId/upgrade { to? } apply it (refused while anything is needed)
@@ -46,7 +46,7 @@ const noUpgrade = (integration) => ({
46
46
  * auth: 'user' = a signed-in user (the workspace is the user's Frigg id), 'admin' = x-frigg-admin-api-key.
47
47
  */
48
48
  const DEFINITION_ROUTES = Object.freeze({
49
- catalogue: { method: 'GET', path: '/api/data-defined/definitions', auth: 'user' },
49
+ catalogue: { method: 'GET', path: '/api/integration-definitions', auth: 'user' },
50
50
  upgradePlan: { method: 'GET', path: '/api/integrations/:integrationId/upgrade', auth: 'user' },
51
51
  upgradeApply: { method: 'POST', path: '/api/integrations/:integrationId/upgrade', auth: 'user' },
52
52
  runs: { method: 'GET', path: '/api/integrations/:integrationId/runs', auth: 'user' },
@@ -71,21 +71,21 @@ const guard = (fn) => catchAsyncError(async (req, res) => { try { await fn(req,
71
71
  * @param {{ execute(req): Promise<{ getId(): string }> }} authenticateUser
72
72
  * @param {{ getIntegrationInstance, integrationClasses: Function[] }} deps
73
73
  */
74
- function setDataDefinedRoutes(router, authenticateUser, { getIntegrationInstance, integrationClasses }) {
75
- const classes = integrationClasses.filter((c) => c.DataDefined);
74
+ function setIntegrationRuntimeRoutes(router, authenticateUser, { getIntegrationInstance, integrationClasses }) {
75
+ const classes = integrationClasses.filter((c) => c.IntegrationRuntime);
76
76
  if (!classes.length) return false;
77
- const catalogClass = classes.find((c) => c.DataDefined.shared) || classes[0];
78
- const { definitions, manifests } = catalogClass.DataDefined;
77
+ const catalogClass = classes.find((c) => c.IntegrationRuntime.shared) || classes[0];
78
+ const { definitions, manifests } = catalogClass.IntegrationRuntime;
79
79
 
80
80
  const installation = async (req) => {
81
81
  const user = await authenticateUser.execute(req);
82
82
  return getIntegrationInstance.execute(req.params.integrationId, user.getId());
83
83
  };
84
- const runtimeOf = (integration) => integration?.constructor?.DataDefined || null;
84
+ const runtimeOf = (integration) => integration?.constructor?.IntegrationRuntime || null;
85
85
 
86
86
  router.route(path('catalogue')).get(guard(async (req, res) => {
87
87
  const user = await authenticateUser.execute(req);
88
- const lists = await Promise.all(classes.map((c) => (c.DataDefined.definitions.list ? c.DataDefined.definitions.list({ workspaceId: String(user.getId()) }) : [])));
88
+ const lists = await Promise.all(classes.map((c) => (c.IntegrationRuntime.definitions.list ? c.IntegrationRuntime.definitions.list({ workspaceId: String(user.getId()) }) : [])));
89
89
  const seen = new Set();
90
90
  res.json({ definitions: lists.flat().filter((d) => (seen.has(d.id) ? false : seen.add(d.id))) });
91
91
  }));
@@ -105,7 +105,7 @@ function setDataDefinedRoutes(router, authenticateUser, { getIntegrationInstance
105
105
 
106
106
  router.route(path('runs')).get(guard(async (req, res) => {
107
107
  const integration = await installation(req);
108
- const store = runtimeOf(integration)?.runs() || catalogClass.DataDefined.runs();
108
+ const store = runtimeOf(integration)?.runs() || catalogClass.IntegrationRuntime.runs();
109
109
  if (!store) return res.json({ runs: [] });
110
110
  const { workflowId, status, before, limit } = req.query;
111
111
  res.json({ runs: await store.listRuns(integration.id, { workflowId, status, before, limit: limit ? Number(limit) : undefined }) });
@@ -167,4 +167,4 @@ function setDataDefinedRoutes(router, authenticateUser, { getIntegrationInstance
167
167
  return true;
168
168
  }
169
169
 
170
- module.exports = { setDataDefinedRoutes, DEFINITION_ROUTES };
170
+ module.exports = { setIntegrationRuntimeRoutes, DEFINITION_ROUTES };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The pieces DataDefinedIntegration wires to Frigg. Each takes its I/O as arguments, so the whole runtime can be
2
+ * The pieces IntegrationRuntime wires to Frigg. Each takes its I/O as arguments, so the whole runtime can be
3
3
  * driven in a test with fakes and the same code runs in Lambda.
4
4
  *
5
5
  * makePorts capability calls go through the module manifest to an Api method; links live in IntegrationMapping
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://friggframework.org/schemas/installation/v0.1.json",
4
- "title": "Data-defined installation config v0.1",
4
+ "title": "Installation config v0.1",
5
5
  "description": "What one customer chose, stored as Integration.config. The definition says what is possible; this says what is on.",
6
6
  "type": "object",
7
7
  "required": [
@@ -1,4 +1,4 @@
1
- # Data-defined integrations: database records (v0.1)
1
+ # Integrations published as definitions: database records (v0.1)
2
2
 
3
3
  Three layers, stored separately. Worked examples for Phone ↔ HubSpot are in `examples/records/`.
4
4
 
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Validate data-defined integration definitions and capability manifests: JSON Schema, then references.
3
+ * Validate integration definitions and capability manifests: JSON Schema, then references.
4
4
  *
5
5
  * node scripts/validate.js every bundled manifest and example
6
6
  * node scripts/validate.js my.definition.json one or more definitions (files or directories)
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Application layer for data-defined integrations (ADR-063): publishing definition versions, the catalogue the
2
+ * Application layer for integrations published as definitions (ADR-063): publishing definition versions, the catalogue the
3
3
  * runtime reads from, and per-installation upgrades. Business rules live here; the repositories only store rows.
4
4
  *
5
5
  * createDefinitionCatalog() what defineSharedIntegrationClass({ definitions }) reads: get(id, version), latest(id)
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Versioning for data-defined integrations.
2
+ * Versioning for integrations published as definitions.
3
3
  *
4
4
  * A published version is immutable and every installation pins one exact version, so a release never changes a
5
5
  * running customer until that installation is upgraded. Three questions are answered here:
@@ -46,6 +46,26 @@ const constantsToBeMigrated = {
46
46
  },
47
47
  };
48
48
 
49
+ /**
50
+ * The facts about a webhook request that signature checks need, small enough to travel in the queue message: the
51
+ * method, the public URL the vendor called, and the raw body (up to 96 KB; beyond that it is left out and a check that
52
+ * needs it fails closed, because SQS messages are capped at 256 KB and the parsed body travels too).
53
+ */
54
+ const RAW_BODY_LIMIT = 96 * 1024;
55
+ function webhookRequestFacts(req) {
56
+ const h = req.headers || {};
57
+ const proto = String(h['x-forwarded-proto'] || req.protocol || 'https').split(',')[0].trim();
58
+ const host = h['x-forwarded-host'] || h.host;
59
+ const url = host ? `${proto}://${host}${req.originalUrl || req.url || ''}` : undefined;
60
+ const raw = req.rawBody;
61
+ const rawBody = raw == null ? undefined : (Buffer.isBuffer(raw) ? raw.toString('utf8') : String(raw));
62
+ return {
63
+ method: req.method,
64
+ ...(url ? { url } : {}),
65
+ ...(rawBody !== undefined && Buffer.byteLength(rawBody) <= RAW_BODY_LIMIT ? { rawBody } : {}),
66
+ };
67
+ }
68
+
49
69
  class IntegrationBase {
50
70
  // todo: maybe we can pass this as Dependency Injection in the sub-class constructor
51
71
  integrationRepository = createIntegrationRepository();
@@ -552,7 +572,7 @@ class IntegrationBase {
552
572
  /**
553
573
  * The recurring triggers declared for one installation (ADR-055): `Definition.triggers`, each with its `name`.
554
574
  * Static so the tick can read them from a record without hydrating the installation; a class whose triggers
555
- * depend on the installation (data-defined integrations, `Definition.instanceTriggers`) overrides it.
575
+ * depend on the installation (integrations published as definitions, `Definition.instanceTriggers`) overrides it.
556
576
  * @param {{ config?: Object, record?: Object }} [installation]
557
577
  * @returns {Promise<Array<Object>>}
558
578
  */
@@ -699,6 +719,7 @@ class IntegrationBase {
699
719
  body,
700
720
  headers: req.headers,
701
721
  query: req.query,
722
+ ...webhookRequestFacts(req),
702
723
  });
703
724
 
704
725
  res.status(200).json({ received: true });
@@ -708,6 +729,15 @@ class IntegrationBase {
708
729
  // Default: no-op, integrations override this
709
730
  }
710
731
 
732
+ /**
733
+ * What a module's verifyWebhookSignature needs about a queued delivery (onWebhookReceived put it there): the
734
+ * headers, the parsed body, the raw body exactly as received, and the method and public URL some vendors sign
735
+ * (HubSpot v3). Call as `this.<module>.api.verifyWebhookSignature(this.webhookVerificationInput(data))`.
736
+ */
737
+ webhookVerificationInput(data = {}) {
738
+ return { headers: data.headers || {}, body: data.body, query: data.query || {}, rawBody: data.rawBody, method: data.method, url: data.url };
739
+ }
740
+
711
741
  /**
712
742
  * Queue a webhook for asynchronous worker dispatch.
713
743
  *
@@ -210,7 +210,7 @@ function createIntegrationRouter() {
210
210
  getPossibleIntegrations,
211
211
  });
212
212
  // Integration versions, run history, sync progress and approvals (ADR-063): mounted when the app registers the integration runtime.
213
- require('./data-defined/router').setDataDefinedRoutes(router, authenticateUser, {
213
+ require('./definitions/router').setIntegrationRuntimeRoutes(router, authenticateUser, {
214
214
  getIntegrationInstance,
215
215
  integrationClasses,
216
216
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@friggframework/core",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.698.b1a2d30.0",
4
+ "version": "2.0.0--canary.699.0d57ee8.0",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
7
7
  "@aws-sdk/client-kms": "^3.588.0",
@@ -47,9 +47,9 @@
47
47
  }
48
48
  },
49
49
  "devDependencies": {
50
- "@friggframework/eslint-config": "2.0.0--canary.698.b1a2d30.0",
51
- "@friggframework/prettier-config": "2.0.0--canary.698.b1a2d30.0",
52
- "@friggframework/test": "2.0.0--canary.698.b1a2d30.0",
50
+ "@friggframework/eslint-config": "2.0.0--canary.699.0d57ee8.0",
51
+ "@friggframework/prettier-config": "2.0.0--canary.699.0d57ee8.0",
52
+ "@friggframework/test": "2.0.0--canary.699.0d57ee8.0",
53
53
  "@prisma/client": "^6.19.3",
54
54
  "@types/lodash": "4.17.15",
55
55
  "@typescript-eslint/eslint-plugin": "^8.0.0",
@@ -67,7 +67,7 @@
67
67
  "scripts": {
68
68
  "lint:fix": "prettier --write --loglevel error . && eslint . --fix",
69
69
  "test": "jest --passWithNoTests # TODO",
70
- "validate:definitions": "node integrations/data-defined/scripts/validate.js",
70
+ "validate:definitions": "node integrations/definitions/scripts/validate.js",
71
71
  "prisma:generate:mongo": "npx prisma generate --schema ./prisma-mongodb/schema.prisma",
72
72
  "prisma:generate:postgres": "npx prisma generate --schema ./prisma-postgresql/schema.prisma",
73
73
  "prisma:generate": "npm run prisma:generate:mongo && npm run prisma:generate:postgres",
@@ -90,5 +90,5 @@
90
90
  "publishConfig": {
91
91
  "access": "public"
92
92
  },
93
- "gitHead": "b1a2d30f3296992d698742a0c0a99b916c7fbfbe"
93
+ "gitHead": "0d57ee8eff44caba64a32685026c122cae03580f"
94
94
  }
@@ -455,7 +455,7 @@ model UsageCounter {
455
455
  @@map("UsageCounter")
456
456
  }
457
457
 
458
- // Data-defined integrations (ADR-063): one immutable row per published definition version.
458
+ // Integrations published as definitions (ADR-063): one immutable row per published definition version.
459
459
  enum DefinitionStatus {
460
460
  DRAFT
461
461
  PUBLISHED
@@ -487,7 +487,7 @@ model IntegrationDefinitionVersion {
487
487
  @@map("IntegrationDefinitionVersion")
488
488
  }
489
489
 
490
- // One row per data-defined workflow run. trace holds step ids, statuses, timings and error messages, never request or
490
+ // One row per workflow run. trace holds step ids, statuses, timings and error messages, never request or
491
491
  // response bodies.
492
492
  model WorkflowRun {
493
493
  id String @id @default(auto()) @map("_id") @db.ObjectId
@@ -436,7 +436,7 @@ model UsageCounter {
436
436
  @@index([metric, integrationType, window])
437
437
  }
438
438
 
439
- // Data-defined integrations (ADR-063): one immutable row per published definition version.
439
+ // Integrations published as definitions (ADR-063): one immutable row per published definition version.
440
440
  enum DefinitionStatus {
441
441
  DRAFT
442
442
  PUBLISHED
@@ -467,7 +467,7 @@ model IntegrationDefinitionVersion {
467
467
  @@index([workspaceId, status])
468
468
  }
469
469
 
470
- // One row per data-defined workflow run. trace holds step ids, statuses, timings and error messages, never request or
470
+ // One row per workflow run. trace holds step ids, statuses, timings and error messages, never request or
471
471
  // response bodies.
472
472
  model WorkflowRun {
473
473
  id Int @id @default(autoincrement())