@friggframework/devtools 2.0.0--canary.596.fc8739c.0 → 2.0.0--canary.597.ea460ba.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.
|
@@ -347,14 +347,46 @@ class IntegrationBuilder extends InfrastructureBuilder {
|
|
|
347
347
|
console.log(` ✓ Webhook handler function defined`);
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
+
// Tier 3 Integration Extension routes (e.g. hubspot.extensions.webhooks).
|
|
351
|
+
// The integration-defined-routers handler already mounts these at
|
|
352
|
+
// runtime; emitting explicit httpApi events here registers them as
|
|
353
|
+
// dedicated routes (visible at startup, and matched ahead of the
|
|
354
|
+
// catch-all) instead of silently relying on {proxy+}. They point at
|
|
355
|
+
// the same handler as the catch-all. Route shape mirrors core's
|
|
356
|
+
// getExtensionRoutes; we read it directly to avoid coupling the
|
|
357
|
+
// build-time generator to a core runtime import.
|
|
358
|
+
const extensionBindings = Object.values(
|
|
359
|
+
integration.Definition.extensions || {}
|
|
360
|
+
);
|
|
361
|
+
const extensionRouteEvents = [];
|
|
362
|
+
for (const binding of extensionBindings) {
|
|
363
|
+
const routes =
|
|
364
|
+
(binding && binding.extension && binding.extension.routes) ||
|
|
365
|
+
[];
|
|
366
|
+
for (const route of routes) {
|
|
367
|
+
extensionRouteEvents.push({
|
|
368
|
+
httpApi: {
|
|
369
|
+
path: `/api/${integrationName}-integration${route.path}`,
|
|
370
|
+
method: route.method,
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (extensionRouteEvents.length > 0) {
|
|
376
|
+
console.log(
|
|
377
|
+
` ✓ ${extensionRouteEvents.length} extension route(s) defined`
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
350
381
|
// Create HTTP API handler for integration (catch-all route AFTER
|
|
351
|
-
// webhooks
|
|
382
|
+
// webhooks and extension routes)
|
|
352
383
|
result.functions[integrationName] = {
|
|
353
384
|
handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${integrationName}.handler`,
|
|
354
385
|
skipEsbuild: true, // Nested exports in node_modules - skip esbuild bundling
|
|
355
386
|
package: functionPackageConfig,
|
|
356
387
|
...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }), // HTTP handlers need Prisma for integration queries
|
|
357
388
|
events: [
|
|
389
|
+
...extensionRouteEvents,
|
|
358
390
|
{
|
|
359
391
|
httpApi: {
|
|
360
392
|
path: `/api/${integrationName}-integration/{proxy+}`,
|
|
@@ -365,54 +397,6 @@ class IntegrationBuilder extends InfrastructureBuilder {
|
|
|
365
397
|
};
|
|
366
398
|
console.log(` ✓ HTTP handler function defined`);
|
|
367
399
|
|
|
368
|
-
// One serverless function per extension binding, namespaced under
|
|
369
|
-
// /{bindingKey}. Prisma layer attached only when useDatabase is true.
|
|
370
|
-
const sanitizeBindingKey = (name) =>
|
|
371
|
-
String(name).replace(/[^A-Za-z0-9]/g, '');
|
|
372
|
-
const extensionEntries = Object.entries(
|
|
373
|
-
integration.Definition.extensions || {}
|
|
374
|
-
);
|
|
375
|
-
for (const [bindingKey, binding] of extensionEntries) {
|
|
376
|
-
const extension = binding && binding.extension;
|
|
377
|
-
const routes = (extension && extension.routes) || [];
|
|
378
|
-
if (routes.length === 0) continue;
|
|
379
|
-
const useDatabase =
|
|
380
|
-
binding.useDatabase ??
|
|
381
|
-
(extension && extension.useDatabase) ??
|
|
382
|
-
false;
|
|
383
|
-
// Wire contract: core's integration-defined-routers derives the
|
|
384
|
-
// identical handler key. Keep both in sync.
|
|
385
|
-
const fnName = `${integrationName}__${sanitizeBindingKey(
|
|
386
|
-
bindingKey
|
|
387
|
-
)}`;
|
|
388
|
-
// Distinct binding keys can sanitize to the same fnName — fail loud rather than overwrite.
|
|
389
|
-
if (
|
|
390
|
-
Object.prototype.hasOwnProperty.call(result.functions, fnName)
|
|
391
|
-
) {
|
|
392
|
-
throw new Error(
|
|
393
|
-
`Integration "${integrationName}" extension function conflict: ` +
|
|
394
|
-
`binding "${bindingKey}" sanitizes to "${fnName}", which is already taken. ` +
|
|
395
|
-
`Use binding keys that are distinct after stripping non-alphanumeric characters.`
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
|
-
result.functions[fnName] = {
|
|
399
|
-
handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${fnName}.handler`,
|
|
400
|
-
skipEsbuild: true,
|
|
401
|
-
package: functionPackageConfig,
|
|
402
|
-
...(usePrismaLayer &&
|
|
403
|
-
useDatabase && { layers: [{ Ref: 'PrismaLambdaLayer' }] }),
|
|
404
|
-
events: routes.map((route) => ({
|
|
405
|
-
httpApi: {
|
|
406
|
-
path: `/api/${integrationName}-integration/${bindingKey}${route.path}`,
|
|
407
|
-
method: route.method,
|
|
408
|
-
},
|
|
409
|
-
})),
|
|
410
|
-
};
|
|
411
|
-
console.log(
|
|
412
|
-
` ✓ Extension handler function defined: ${fnName} (useDatabase: ${useDatabase})`
|
|
413
|
-
);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
400
|
// Create Queue Worker function
|
|
417
401
|
const queueWorkerName = `${integrationName}QueueWorker`;
|
|
418
402
|
result.functions[queueWorkerName] = {
|
|
@@ -568,7 +568,7 @@ describe('IntegrationBuilder', () => {
|
|
|
568
568
|
]);
|
|
569
569
|
});
|
|
570
570
|
|
|
571
|
-
it('
|
|
571
|
+
it('should emit dedicated httpApi events for Tier 3 extension routes, before the catch-all', async () => {
|
|
572
572
|
const appDefinition = {
|
|
573
573
|
integrations: [
|
|
574
574
|
{
|
|
@@ -602,24 +602,15 @@ describe('IntegrationBuilder', () => {
|
|
|
602
602
|
|
|
603
603
|
const result = await integrationBuilder.build(appDefinition, {});
|
|
604
604
|
|
|
605
|
-
//
|
|
606
|
-
//
|
|
607
|
-
|
|
608
|
-
expect(fn).toBeDefined();
|
|
609
|
-
expect(fn.handler).toBe(
|
|
610
|
-
'node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.hubspot__hubspotWebhooks.handler'
|
|
611
|
-
);
|
|
612
|
-
expect(fn.events).toEqual([
|
|
605
|
+
// The extension route is registered on the same handler as the
|
|
606
|
+
// catch-all, and ordered before {proxy+}.
|
|
607
|
+
expect(result.functions.hubspot.events).toEqual([
|
|
613
608
|
{
|
|
614
609
|
httpApi: {
|
|
615
|
-
path: '/api/hubspot-integration/
|
|
610
|
+
path: '/api/hubspot-integration/webhooks',
|
|
616
611
|
method: 'POST',
|
|
617
612
|
},
|
|
618
613
|
},
|
|
619
|
-
]);
|
|
620
|
-
|
|
621
|
-
// The main integration handler keeps only the catch-all.
|
|
622
|
-
expect(result.functions.hubspot.events).toEqual([
|
|
623
614
|
{
|
|
624
615
|
httpApi: {
|
|
625
616
|
path: '/api/hubspot-integration/{proxy+}',
|
|
@@ -627,69 +618,6 @@ describe('IntegrationBuilder', () => {
|
|
|
627
618
|
},
|
|
628
619
|
},
|
|
629
620
|
]);
|
|
630
|
-
|
|
631
|
-
// useDatabase defaults to false → no Prisma layer on the receiver.
|
|
632
|
-
expect(fn.layers).toBeUndefined();
|
|
633
|
-
});
|
|
634
|
-
|
|
635
|
-
it('attaches the Prisma layer to a per-binding function only when useDatabase is true', async () => {
|
|
636
|
-
const mkDef = (useDatabase) => ({
|
|
637
|
-
integrations: [
|
|
638
|
-
{
|
|
639
|
-
Definition: {
|
|
640
|
-
name: 'hs',
|
|
641
|
-
extensions: {
|
|
642
|
-
wh: {
|
|
643
|
-
extension: {
|
|
644
|
-
name: 'wh-ext',
|
|
645
|
-
useDatabase,
|
|
646
|
-
routes: [
|
|
647
|
-
{
|
|
648
|
-
path: '/webhooks',
|
|
649
|
-
method: 'POST',
|
|
650
|
-
event: 'E',
|
|
651
|
-
},
|
|
652
|
-
],
|
|
653
|
-
events: { E: { handler: () => {} } },
|
|
654
|
-
},
|
|
655
|
-
},
|
|
656
|
-
},
|
|
657
|
-
},
|
|
658
|
-
},
|
|
659
|
-
],
|
|
660
|
-
});
|
|
661
|
-
|
|
662
|
-
const withDb = await integrationBuilder.build(mkDef(true), {});
|
|
663
|
-
expect(withDb.functions.hs__wh.layers).toEqual([
|
|
664
|
-
{ Ref: 'PrismaLambdaLayer' },
|
|
665
|
-
]);
|
|
666
|
-
|
|
667
|
-
const withoutDb = await integrationBuilder.build(mkDef(false), {});
|
|
668
|
-
expect(withoutDb.functions.hs__wh.layers).toBeUndefined();
|
|
669
|
-
});
|
|
670
|
-
|
|
671
|
-
it('throws when two binding keys sanitize to the same function name', async () => {
|
|
672
|
-
const mkExt = (name, event) => ({
|
|
673
|
-
name,
|
|
674
|
-
routes: [{ path: '/w', method: 'POST', event }],
|
|
675
|
-
events: { [event]: { handler: () => {} } },
|
|
676
|
-
});
|
|
677
|
-
const appDefinition = {
|
|
678
|
-
integrations: [
|
|
679
|
-
{
|
|
680
|
-
Definition: {
|
|
681
|
-
name: 'hs',
|
|
682
|
-
extensions: {
|
|
683
|
-
'hub-spot': { extension: mkExt('a', 'A') }, // → hs__hubspot
|
|
684
|
-
hubspot: { extension: mkExt('b', 'B') }, // → hs__hubspot
|
|
685
|
-
},
|
|
686
|
-
},
|
|
687
|
-
},
|
|
688
|
-
],
|
|
689
|
-
};
|
|
690
|
-
await expect(
|
|
691
|
-
integrationBuilder.build(appDefinition, {})
|
|
692
|
-
).rejects.toThrow(/extension function conflict.*hs__hubspot/);
|
|
693
621
|
});
|
|
694
622
|
|
|
695
623
|
it('should only have the catch-all proxy route when no extensions are declared', async () => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/devtools",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.
|
|
4
|
+
"version": "2.0.0--canary.597.ea460ba.0",
|
|
5
5
|
"bin": {
|
|
6
6
|
"frigg": "./frigg-cli/index.js"
|
|
7
7
|
},
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"@babel/eslint-parser": "^7.18.9",
|
|
26
26
|
"@babel/parser": "^7.25.3",
|
|
27
27
|
"@babel/traverse": "^7.25.3",
|
|
28
|
-
"@friggframework/core": "2.0.0--canary.
|
|
29
|
-
"@friggframework/schemas": "2.0.0--canary.
|
|
30
|
-
"@friggframework/test": "2.0.0--canary.
|
|
28
|
+
"@friggframework/core": "2.0.0--canary.597.ea460ba.0",
|
|
29
|
+
"@friggframework/schemas": "2.0.0--canary.597.ea460ba.0",
|
|
30
|
+
"@friggframework/test": "2.0.0--canary.597.ea460ba.0",
|
|
31
31
|
"@hapi/boom": "^10.0.1",
|
|
32
32
|
"@inquirer/prompts": "^5.3.8",
|
|
33
33
|
"axios": "^1.7.2",
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"validate-npm-package-name": "^5.0.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@friggframework/eslint-config": "2.0.0--canary.
|
|
59
|
-
"@friggframework/prettier-config": "2.0.0--canary.
|
|
58
|
+
"@friggframework/eslint-config": "2.0.0--canary.597.ea460ba.0",
|
|
59
|
+
"@friggframework/prettier-config": "2.0.0--canary.597.ea460ba.0",
|
|
60
60
|
"aws-sdk-client-mock": "^4.1.0",
|
|
61
61
|
"aws-sdk-client-mock-jest": "^4.1.0",
|
|
62
62
|
"jest": "^30.1.3",
|
|
@@ -88,5 +88,5 @@
|
|
|
88
88
|
"publishConfig": {
|
|
89
89
|
"access": "public"
|
|
90
90
|
},
|
|
91
|
-
"gitHead": "
|
|
91
|
+
"gitHead": "ea460ba4cff4f584ecd38c0973dc966f09ee1ca8"
|
|
92
92
|
}
|