@friggframework/core 2.0.0--canary.698.b1a2d30.0 → 2.0.0--canary.700.80ec1cc.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 +104 -75
  15. package/integrations/{data-defined → definitions}/capabilities/asana.capabilities.json +13 -8
  16. package/integrations/{data-defined → definitions}/capabilities/fathom.capabilities.json +4 -4
  17. package/integrations/{data-defined → definitions}/capabilities/hubspot.capabilities.json +77 -49
  18. package/integrations/{data-defined → definitions}/capabilities/linear.capabilities.json +38 -20
  19. package/integrations/{data-defined → definitions}/capabilities/phone.capabilities.json +1 -2
  20. package/integrations/{data-defined → definitions}/capabilities/pipedrive.capabilities.json +8 -6
  21. package/integrations/{data-defined → definitions}/capabilities/salesforce.capabilities.json +30 -15
  22. package/integrations/{data-defined → definitions}/capabilities/slack.capabilities.json +7 -4
  23. package/integrations/{data-defined → definitions}/capabilities/stripe.capabilities.json +27 -18
  24. package/integrations/{data-defined → definitions}/config-form.js +1 -1
  25. package/integrations/{data-defined → definitions}/examples/fathom-hubspot-notion.definition.json +9 -9
  26. package/integrations/{data-defined → definitions}/examples/hubspot-slack.definition.json +25 -7
  27. package/integrations/{data-defined → definitions}/examples/phone-hubspot.definition.json +1 -1
  28. package/integrations/{data-defined → definitions}/examples/pipedrive-hubspot.definition.json +1 -1
  29. package/integrations/{data-defined → definitions}/examples/records/phone-hubspot.definition-version.json +1 -1
  30. package/integrations/{data-defined → definitions}/examples/records/phone-hubspot.integration.json +1 -1
  31. package/integrations/{data-defined → definitions}/expressions.js +1 -1
  32. package/integrations/{data-defined → definitions}/index.js +2 -2
  33. package/integrations/{data-defined/data-defined-integration.js → definitions/integration-runtime.js} +22 -21
  34. package/integrations/{data-defined → definitions}/renderers/hubspot.js +13 -8
  35. package/integrations/{data-defined → definitions}/router.js +11 -11
  36. package/integrations/{data-defined → definitions}/runtime.js +1 -1
  37. package/integrations/{data-defined → definitions}/schema/installation.v0.1.schema.json +1 -1
  38. package/integrations/{data-defined → definitions}/schema/records.md +1 -1
  39. package/integrations/{data-defined → definitions}/scripts/validate.js +1 -1
  40. package/integrations/{data-defined → definitions}/use-cases/definition-use-cases.js +1 -1
  41. package/integrations/{data-defined → definitions}/versioning.js +1 -1
  42. package/integrations/integration-base.js +31 -1
  43. package/integrations/integration-router.js +1 -1
  44. package/package.json +6 -6
  45. package/prisma-mongodb/schema.prisma +2 -2
  46. package/prisma-postgresql/schema.prisma +2 -2
  47. /package/integrations/{data-defined → definitions}/canonical.js +0 -0
  48. /package/integrations/{data-defined → definitions}/capabilities/acmeerp.capabilities.json +0 -0
  49. /package/integrations/{data-defined → definitions}/capabilities/calendly.capabilities.json +0 -0
  50. /package/integrations/{data-defined → definitions}/capabilities/docusign.capabilities.json +0 -0
  51. /package/integrations/{data-defined → definitions}/capabilities/github.capabilities.json +0 -0
  52. /package/integrations/{data-defined → definitions}/capabilities/jira.capabilities.json +0 -0
  53. /package/integrations/{data-defined → definitions}/capabilities/klaviyo.capabilities.json +0 -0
  54. /package/integrations/{data-defined → definitions}/capabilities/netsuite.capabilities.json +0 -0
  55. /package/integrations/{data-defined → definitions}/capabilities/notion.capabilities.json +0 -0
  56. /package/integrations/{data-defined → definitions}/capabilities/quickbooks.capabilities.json +0 -0
  57. /package/integrations/{data-defined → definitions}/capabilities/shopify.capabilities.json +0 -0
  58. /package/integrations/{data-defined → definitions}/capabilities/webflow.capabilities.json +0 -0
  59. /package/integrations/{data-defined → definitions}/capabilities/zendesk.capabilities.json +0 -0
  60. /package/integrations/{data-defined → definitions}/compile.js +0 -0
  61. /package/integrations/{data-defined → definitions}/examples/acme-erp-quickbooks.definition.json +0 -0
  62. /package/integrations/{data-defined → definitions}/examples/asana-slack.definition.json +0 -0
  63. /package/integrations/{data-defined → definitions}/examples/calendly-salesforce.definition.json +0 -0
  64. /package/integrations/{data-defined → definitions}/examples/docusign-salesforce.definition.json +0 -0
  65. /package/integrations/{data-defined → definitions}/examples/github-linear.definition.json +0 -0
  66. /package/integrations/{data-defined → definitions}/examples/klaviyo-hubspot.definition.json +0 -0
  67. /package/integrations/{data-defined → definitions}/examples/pipedrive-phone.definition.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
@@ -472,8 +472,7 @@
472
472
  "eventId": "{{body.id}}",
473
473
  "account": "{{body.account}}",
474
474
  "verify": "verifyWebhookSignature",
475
- "status": "unimplemented",
476
- "note": "The stripe Api has no verifyWebhookSignature method; webhook signatures cannot be checked until the module adds one."
475
+ "note": "Stripe-Signature t=…,v1=… over t.raw body, 300 s tolerance; rolling secrets accepted. Implemented in api-module-library 659b90d; needs the module release that includes it."
477
476
  },
478
477
  "capabilities": {
479
478
  "customer.get": {
@@ -486,8 +485,9 @@
486
485
  }
487
486
  },
488
487
  "output": "customer",
489
- "status": "unimplemented",
490
- "note": "Api wraps balance transactions, charges, accounts and webhook endpoints only; customers, payment intents, refunds and payouts are not exposed."
488
+ "args": [
489
+ "id"
490
+ ]
491
491
  },
492
492
  "customer.list": {
493
493
  "kind": "read",
@@ -504,8 +504,9 @@
504
504
  }
505
505
  },
506
506
  "output": "page<customer>",
507
- "status": "unimplemented",
508
- "note": "Api wraps balance transactions, charges, accounts and webhook endpoints only; customers, payment intents, refunds and payouts are not exposed."
507
+ "args": [
508
+ "$rest"
509
+ ]
509
510
  },
510
511
  "customer.search": {
511
512
  "kind": "search",
@@ -517,8 +518,9 @@
517
518
  }
518
519
  },
519
520
  "output": "customer[]",
520
- "status": "unimplemented",
521
- "note": "Api wraps balance transactions, charges, accounts and webhook endpoints only; customers, payment intents, refunds and payouts are not exposed."
521
+ "args": [
522
+ "query"
523
+ ]
522
524
  },
523
525
  "customer.create": {
524
526
  "kind": "write",
@@ -530,8 +532,9 @@
530
532
  }
531
533
  },
532
534
  "output": "customer",
533
- "status": "unimplemented",
534
- "note": "Api wraps balance transactions, charges, accounts and webhook endpoints only; customers, payment intents, refunds and payouts are not exposed."
535
+ "args": [
536
+ "params"
537
+ ]
535
538
  },
536
539
  "customer.update": {
537
540
  "kind": "write",
@@ -547,8 +550,10 @@
547
550
  }
548
551
  },
549
552
  "output": "customer",
550
- "status": "unimplemented",
551
- "note": "Api wraps balance transactions, charges, accounts and webhook endpoints only; customers, payment intents, refunds and payouts are not exposed."
553
+ "args": [
554
+ "id",
555
+ "params"
556
+ ]
552
557
  },
553
558
  "paymentIntent.get": {
554
559
  "kind": "read",
@@ -563,8 +568,10 @@
563
568
  }
564
569
  },
565
570
  "output": "paymentIntent",
566
- "status": "unimplemented",
567
- "note": "Api wraps balance transactions, charges, accounts and webhook endpoints only; customers, payment intents, refunds and payouts are not exposed."
571
+ "args": [
572
+ "id",
573
+ "$rest"
574
+ ]
568
575
  },
569
576
  "refund.get": {
570
577
  "kind": "read",
@@ -576,8 +583,9 @@
576
583
  }
577
584
  },
578
585
  "output": "refund",
579
- "status": "unimplemented",
580
- "note": "Api wraps balance transactions, charges, accounts and webhook endpoints only; customers, payment intents, refunds and payouts are not exposed."
586
+ "args": [
587
+ "id"
588
+ ]
581
589
  },
582
590
  "payout.list": {
583
591
  "kind": "read",
@@ -597,8 +605,9 @@
597
605
  }
598
606
  },
599
607
  "output": "payout[]",
600
- "status": "unimplemented",
601
- "note": "Api wraps balance transactions, charges, accounts and webhook endpoints only; customers, payment intents, refunds and payouts are not exposed."
608
+ "args": [
609
+ "$rest"
610
+ ]
602
611
  },
603
612
  "balanceTransaction.list": {
604
613
  "kind": "read",
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The configure form for a data-defined installation, generated from its definition: what GET_CONFIG_OPTIONS and
2
+ * The configure form for an installation, generated from its definition: what GET_CONFIG_OPTIONS and
3
3
  * REFRESH_CONFIG_OPTIONS return on Frigg's existing, authenticated /api/integrations/:id/config/options routes.
4
4
  *
5
5
  * The form's data has exactly the shape of Integration.config, so what the management UI PATCHes back is the config:
@@ -79,7 +79,7 @@
79
79
  },
80
80
  {
81
81
  "use": "stop",
82
- "when": "$.steps.contacts[0] == null",
82
+ "when": "$.steps.contacts.results[0] == null",
83
83
  "with": {
84
84
  "reason": "Attendee is not a HubSpot contact"
85
85
  }
@@ -88,7 +88,7 @@
88
88
  "id": "deals",
89
89
  "use": "hubspot.deal.listForContact",
90
90
  "with": {
91
- "contactId": "$.steps.contacts[0].id",
91
+ "contactId": "$.steps.contacts.results[0].id",
92
92
  "open": true
93
93
  }
94
94
  },
@@ -102,7 +102,7 @@
102
102
  {
103
103
  "use": "hubspot.note.create",
104
104
  "with": {
105
- "contactId": "$.steps.contacts[0].id",
105
+ "contactId": "$.steps.contacts.results[0].id",
106
106
  "dealId": "$.steps.deals[0].id",
107
107
  "properties": {
108
108
  "hs_note_body": "$.steps.body",
@@ -163,7 +163,7 @@
163
163
  },
164
164
  {
165
165
  "use": "stop",
166
- "when": "$.steps.contacts[0] == null",
166
+ "when": "$.steps.contacts.results[0] == null",
167
167
  "with": {
168
168
  "reason": "Attendee is not a HubSpot contact"
169
169
  }
@@ -189,7 +189,7 @@
189
189
  "use": "hubspot.task.create",
190
190
  "when": "$.item.completed != true",
191
191
  "with": {
192
- "contactId": "$.steps.contacts[0].id",
192
+ "contactId": "$.steps.contacts.results[0].id",
193
193
  "properties": {
194
194
  "hs_task_subject": "{{item.description | truncate: 120}}",
195
195
  "hs_task_body": "From \"{{record.title}}\" at {{item.recording_timestamp}}: {{record.share_url}}",
@@ -329,7 +329,7 @@
329
329
  },
330
330
  {
331
331
  "use": "stop",
332
- "when": "$.steps.contacts[0] == null",
332
+ "when": "$.steps.contacts.results[0] == null",
333
333
  "with": {
334
334
  "reason": "Attendee is not a HubSpot contact"
335
335
  }
@@ -338,7 +338,7 @@
338
338
  "id": "deals",
339
339
  "use": "hubspot.deal.listForContact",
340
340
  "with": {
341
- "contactId": "$.steps.contacts[0].id",
341
+ "contactId": "$.steps.contacts.results[0].id",
342
342
  "open": true
343
343
  }
344
344
  },
@@ -399,7 +399,7 @@
399
399
  {
400
400
  "use": "hubspot.deal.update",
401
401
  "with": {
402
- "id": "$.steps.deals[0].id",
402
+ "dealId": "$.steps.deals[0].id",
403
403
  "properties": {
404
404
  "hs_next_step": "$.steps.proposal.nextStep"
405
405
  }
@@ -432,7 +432,7 @@
432
432
  {
433
433
  "use": "hubspot.deal.update",
434
434
  "with": {
435
- "id": "$.record.metadata.event_payload.dealId",
435
+ "dealId": "$.record.metadata.event_payload.dealId",
436
436
  "properties": {
437
437
  "dealstage": "$.record.metadata.event_payload.stage",
438
438
  "hs_next_step": "$.record.metadata.event_payload.nextStep"
@@ -159,15 +159,33 @@
159
159
  "id": "deals",
160
160
  "use": "hubspot.deal.search",
161
161
  "with": {
162
- "open": true,
163
- "closeDateBefore": "+7d",
164
- "minAmount": "$.settings.minAmount",
162
+ "filterGroups": [
163
+ {
164
+ "filters": [
165
+ {
166
+ "propertyName": "hs_is_closed",
167
+ "operator": "EQ",
168
+ "value": "false"
169
+ },
170
+ {
171
+ "propertyName": "closedate",
172
+ "operator": "LT",
173
+ "value": "{{now | to_unix | plus: 604800 | times: 1000}}"
174
+ },
175
+ {
176
+ "propertyName": "amount",
177
+ "operator": "GTE",
178
+ "value": "$.settings.minAmount"
179
+ }
180
+ ]
181
+ }
182
+ ],
165
183
  "limit": 50
166
184
  }
167
185
  },
168
186
  {
169
187
  "use": "stop",
170
- "when": "$.steps.deals[0] == null",
188
+ "when": "$.steps.deals.results[0] == null",
171
189
  "with": {
172
190
  "reason": "Nothing closes this week"
173
191
  }
@@ -176,7 +194,7 @@
176
194
  "id": "build",
177
195
  "use": "foreach",
178
196
  "with": {
179
- "over": "$.steps.deals",
197
+ "over": "$.steps.deals.results",
180
198
  "max": 50,
181
199
  "collect": "$.steps.line"
182
200
  },
@@ -231,7 +249,7 @@
231
249
  "id": "remind",
232
250
  "use": "foreach",
233
251
  "with": {
234
- "over": "$.steps.tasks",
252
+ "over": "$.steps.tasks.results",
235
253
  "max": 100
236
254
  },
237
255
  "do": [
@@ -404,6 +422,6 @@
404
422
  "deal-won": ":tada: *{{steps.deal.properties.dealname | mrkdwn}}* closed won for ${{steps.deal.properties.amount | default: '?'}}. Nice work {{steps.owner.firstName | default: 'team' | mrkdwn}}! <https://app.hubspot.com/contacts/{{install.portalId}}/record/0-3/{{steps.deal.id}}|Open deal>",
405
423
  "high-value": ":moneybag: New deal over ${{settings.threshold}}: *{{steps.deal.properties.dealname | mrkdwn}}* (${{steps.deal.properties.amount}}), closing {{steps.deal.properties.closedate | date}}. <https://app.hubspot.com/contacts/{{install.portalId}}/record/0-3/{{steps.deal.id}}|Open deal>",
406
424
  "digest-line": "\u2022 <https://app.hubspot.com/contacts/{{install.portalId}}/record/0-3/{{item.id}}|{{item.properties.dealname | mrkdwn}}>: ${{item.properties.amount | default: 0}}, closes {{item.properties.closedate | date}}",
407
- "digest": ":calendar: *Closing in the next 7 days* ({{steps.deals | size}} deals)\n{{steps.build | join: '\\n'}}"
425
+ "digest": ":calendar: *Closing in the next 7 days* ({{steps.deals.results | size}} deals)\n{{steps.build | join: '\\n'}}"
408
426
  }
409
427
  }
@@ -381,7 +381,7 @@
381
381
  "id": "each",
382
382
  "use": "foreach",
383
383
  "with": {
384
- "over": "$.steps.stale",
384
+ "over": "$.steps.stale.results",
385
385
  "max": 100
386
386
  },
387
387
  "do": [
@@ -5,7 +5,7 @@
5
5
  "version": "1.0.0",
6
6
  "display": {
7
7
  "label": "Pipedrive \u21c4 HubSpot people",
8
- "description": "Keeps Pipedrive people and HubSpot contacts the same person on both sides, matched by email then phone. Sync only: no workflows, no UI.",
8
+ "description": "Keeps Pipedrive people and HubSpot contacts the same person on both sides, matched by email, then by phone.",
9
9
  "category": "CRM",
10
10
  "tags": [
11
11
  "crm",
@@ -399,7 +399,7 @@
399
399
  "id": "each",
400
400
  "use": "foreach",
401
401
  "with": {
402
- "over": "$.steps.stale",
402
+ "over": "$.steps.stale.results",
403
403
  "max": 100
404
404
  },
405
405
  "do": [
@@ -69,7 +69,7 @@
69
69
  "installed": {
70
70
  "hook": {
71
71
  "id": "WHk2c9",
72
- "url": "https://api.example.com/api/data-defined-integration/webhooks/int_7f3a9c?module=phone",
72
+ "url": "https://api.example.com/api/integration-runtime-integration/webhooks/int_7f3a9c?module=phone",
73
73
  "events": [
74
74
  "call.completed",
75
75
  "call.ringing",
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Expressions for data-defined integrations (v0.1).
2
+ * Expressions for integrations published as definitions (v0.1).
3
3
  *
4
4
  * Three forms, and no others (nothing is ever passed to eval):
5
5
  * 1. Reference "$.event.contact.id" A string that is exactly a path. Resolves to the value, keeping its type.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Data-defined integrations, v0.1 (ADR-063). An integration as a document: modules, syncs, workflows, queries,
2
+ * Integrations published as definitions, v0.1 (ADR-063). An integration as a document: modules, syncs, workflows, queries,
3
3
  * UI surfaces and templates, run by one runtime class on the existing Frigg infrastructure.
4
4
  */
5
5
  const fs = require('fs');
@@ -9,7 +9,7 @@ const { compile, routeWebhook, eventName } = require('./compile');
9
9
  const { execute } = require('./interpreter');
10
10
  const { makePorts, runWorkflow, runQuery, renderSurface, resolveView, runLifecycle, resolveOptions } = require('./runtime');
11
11
  const { diff, checkBump, planUpgrade } = require('./versioning');
12
- const { defineIntegrationClass, defineSharedIntegrationClass, extendIntegrationClass, memoryDefinitions } = require('./data-defined-integration');
12
+ const { defineIntegrationClass, defineSharedIntegrationClass, extendIntegrationClass, memoryDefinitions } = require('./integration-runtime');
13
13
  const { BUILTINS } = require('./steps');
14
14
  const { TRANSFORMS, applyMapping } = require('./transforms');
15
15
  const { FILTERS } = require('./expressions');
@@ -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)