@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.
- package/generated/prisma-mongodb/edge.js +2 -2
- package/generated/prisma-mongodb/index.js +2 -2
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +2 -2
- package/generated/prisma-mongodb/wasm.js +2 -2
- package/generated/prisma-postgresql/edge.js +2 -2
- package/generated/prisma-postgresql/index.js +2 -2
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +2 -2
- package/generated/prisma-postgresql/wasm.js +2 -2
- package/handlers/app-handler-helpers.js +5 -2
- package/handlers/integration-event-dispatcher.js +1 -1
- package/integrations/{data-defined → definitions}/README.md +12 -12
- package/integrations/{data-defined → definitions}/capabilities/AUDIT.md +104 -75
- package/integrations/{data-defined → definitions}/capabilities/asana.capabilities.json +13 -8
- package/integrations/{data-defined → definitions}/capabilities/fathom.capabilities.json +4 -4
- package/integrations/{data-defined → definitions}/capabilities/hubspot.capabilities.json +77 -49
- package/integrations/{data-defined → definitions}/capabilities/linear.capabilities.json +38 -20
- package/integrations/{data-defined → definitions}/capabilities/phone.capabilities.json +1 -2
- package/integrations/{data-defined → definitions}/capabilities/pipedrive.capabilities.json +8 -6
- package/integrations/{data-defined → definitions}/capabilities/salesforce.capabilities.json +30 -15
- package/integrations/{data-defined → definitions}/capabilities/slack.capabilities.json +7 -4
- package/integrations/{data-defined → definitions}/capabilities/stripe.capabilities.json +27 -18
- package/integrations/{data-defined → definitions}/config-form.js +1 -1
- package/integrations/{data-defined → definitions}/examples/fathom-hubspot-notion.definition.json +9 -9
- package/integrations/{data-defined → definitions}/examples/hubspot-slack.definition.json +25 -7
- package/integrations/{data-defined → definitions}/examples/phone-hubspot.definition.json +1 -1
- package/integrations/{data-defined → definitions}/examples/pipedrive-hubspot.definition.json +1 -1
- package/integrations/{data-defined → definitions}/examples/records/phone-hubspot.definition-version.json +1 -1
- package/integrations/{data-defined → definitions}/examples/records/phone-hubspot.integration.json +1 -1
- package/integrations/{data-defined → definitions}/expressions.js +1 -1
- package/integrations/{data-defined → definitions}/index.js +2 -2
- package/integrations/{data-defined/data-defined-integration.js → definitions/integration-runtime.js} +22 -21
- package/integrations/{data-defined → definitions}/renderers/hubspot.js +13 -8
- package/integrations/{data-defined → definitions}/router.js +11 -11
- package/integrations/{data-defined → definitions}/runtime.js +1 -1
- package/integrations/{data-defined → definitions}/schema/installation.v0.1.schema.json +1 -1
- package/integrations/{data-defined → definitions}/schema/records.md +1 -1
- package/integrations/{data-defined → definitions}/scripts/validate.js +1 -1
- package/integrations/{data-defined → definitions}/use-cases/definition-use-cases.js +1 -1
- package/integrations/{data-defined → definitions}/versioning.js +1 -1
- package/integrations/integration-base.js +31 -1
- package/integrations/integration-router.js +1 -1
- package/package.json +6 -6
- package/prisma-mongodb/schema.prisma +2 -2
- package/prisma-postgresql/schema.prisma +2 -2
- /package/integrations/{data-defined → definitions}/canonical.js +0 -0
- /package/integrations/{data-defined → definitions}/capabilities/acmeerp.capabilities.json +0 -0
- /package/integrations/{data-defined → definitions}/capabilities/calendly.capabilities.json +0 -0
- /package/integrations/{data-defined → definitions}/capabilities/docusign.capabilities.json +0 -0
- /package/integrations/{data-defined → definitions}/capabilities/github.capabilities.json +0 -0
- /package/integrations/{data-defined → definitions}/capabilities/jira.capabilities.json +0 -0
- /package/integrations/{data-defined → definitions}/capabilities/klaviyo.capabilities.json +0 -0
- /package/integrations/{data-defined → definitions}/capabilities/netsuite.capabilities.json +0 -0
- /package/integrations/{data-defined → definitions}/capabilities/notion.capabilities.json +0 -0
- /package/integrations/{data-defined → definitions}/capabilities/quickbooks.capabilities.json +0 -0
- /package/integrations/{data-defined → definitions}/capabilities/shopify.capabilities.json +0 -0
- /package/integrations/{data-defined → definitions}/capabilities/webflow.capabilities.json +0 -0
- /package/integrations/{data-defined → definitions}/capabilities/zendesk.capabilities.json +0 -0
- /package/integrations/{data-defined → definitions}/compile.js +0 -0
- /package/integrations/{data-defined → definitions}/examples/acme-erp-quickbooks.definition.json +0 -0
- /package/integrations/{data-defined → definitions}/examples/asana-slack.definition.json +0 -0
- /package/integrations/{data-defined → definitions}/examples/calendly-salesforce.definition.json +0 -0
- /package/integrations/{data-defined → definitions}/examples/docusign-salesforce.definition.json +0 -0
- /package/integrations/{data-defined → definitions}/examples/github-linear.definition.json +0 -0
- /package/integrations/{data-defined → definitions}/examples/klaviyo-hubspot.definition.json +0 -0
- /package/integrations/{data-defined → definitions}/examples/pipedrive-phone.definition.json +0 -0
- /package/integrations/{data-defined → definitions}/examples/records/phone-hubspot.mappings.json +0 -0
- /package/integrations/{data-defined → definitions}/examples/records/phone-hubspot.workflow-run.json +0 -0
- /package/integrations/{data-defined → definitions}/examples/shopify-klaviyo.definition.json +0 -0
- /package/integrations/{data-defined → definitions}/examples/shopify-netsuite.definition.json +0 -0
- /package/integrations/{data-defined → definitions}/examples/stripe-quickbooks.definition.json +0 -0
- /package/integrations/{data-defined → definitions}/examples/webflow-hubspot-slack.definition.json +0 -0
- /package/integrations/{data-defined → definitions}/examples/zendesk-jira.definition.json +0 -0
- /package/integrations/{data-defined → definitions}/interpreter.js +0 -0
- /package/integrations/{data-defined → definitions}/manifests.js +0 -0
- /package/integrations/{data-defined → definitions}/renderers/common.js +0 -0
- /package/integrations/{data-defined → definitions}/renderers/html.js +0 -0
- /package/integrations/{data-defined → definitions}/renderers/preview.js +0 -0
- /package/integrations/{data-defined → definitions}/renderers/slack.js +0 -0
- /package/integrations/{data-defined → definitions}/repositories/definition-version-repository.js +0 -0
- /package/integrations/{data-defined → definitions}/repositories/workflow-run-repository.js +0 -0
- /package/integrations/{data-defined → definitions}/schema/capability-manifest.v0.1.schema.json +0 -0
- /package/integrations/{data-defined → definitions}/schema/integration-definition.v0.1.schema.json +0 -0
- /package/integrations/{data-defined → definitions}/schema-check.js +0 -0
- /package/integrations/{data-defined → definitions}/scripts/check-manifests.js +0 -0
- /package/integrations/{data-defined → definitions}/steps.js +0 -0
- /package/integrations/{data-defined → definitions}/sync-engine.js +0 -0
- /package/integrations/{data-defined → definitions}/tests/helpers-fake-crm.js +0 -0
- /package/integrations/{data-defined → definitions}/transforms.js +0 -0
- /package/integrations/{data-defined → definitions}/validate.js +0 -0
- /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
|
-
"
|
|
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
|
-
"
|
|
490
|
-
|
|
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
|
-
"
|
|
508
|
-
|
|
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
|
-
"
|
|
521
|
-
|
|
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
|
-
"
|
|
534
|
-
|
|
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
|
-
"
|
|
551
|
-
|
|
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
|
-
"
|
|
567
|
-
|
|
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
|
-
"
|
|
580
|
-
|
|
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
|
-
"
|
|
601
|
-
|
|
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
|
|
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:
|
package/integrations/{data-defined → definitions}/examples/fathom-hubspot-notion.definition.json
RENAMED
|
@@ -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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
163
|
-
|
|
164
|
-
|
|
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
|
}
|
package/integrations/{data-defined → definitions}/examples/pipedrive-hubspot.definition.json
RENAMED
|
@@ -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.
|
|
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",
|
package/integrations/{data-defined → definitions}/examples/records/phone-hubspot.integration.json
RENAMED
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"installed": {
|
|
70
70
|
"hook": {
|
|
71
71
|
"id": "WHk2c9",
|
|
72
|
-
"url": "https://api.example.com/api/
|
|
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
|
|
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
|
-
*
|
|
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('./
|
|
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');
|
package/integrations/{data-defined/data-defined-integration.js → definitions/integration-runtime.js}
RENAMED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* IntegrationRuntime: one IntegrationBase subclass that runs any published definition.
|
|
3
3
|
*
|
|
4
|
-
* Frigg registers integration classes at startup (appDefinition.integrations). For
|
|
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/
|
|
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
|
|
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 "
|
|
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 = '
|
|
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: '
|
|
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
|
|
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
|
|
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
|
|
194
|
-
static
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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(
|
|
682
|
-
if (!defaultId)
|
|
683
|
-
return
|
|
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
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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', {
|
|
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
|
-
|
|
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, ...(
|
|
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 `
|
|
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/
|
|
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/
|
|
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
|
|
75
|
-
const classes = integrationClasses.filter((c) => c.
|
|
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.
|
|
78
|
-
const { definitions, manifests } = catalogClass.
|
|
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?.
|
|
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.
|
|
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.
|
|
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 = {
|
|
170
|
+
module.exports = { setIntegrationRuntimeRoutes, DEFINITION_ROUTES };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The pieces
|
|
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": "
|
|
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,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Validate
|
|
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)
|