@friggframework/devtools 2.0.0--canary.590.c1af198.0 → 2.0.0--canary.596.6355e72.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,46 +347,15 @@ 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
-
381
350
  // Create HTTP API handler for integration (catch-all route AFTER
382
- // webhooks and extension routes)
351
+ // webhooks). Tier 3 extension routes are NOT folded in here — each
352
+ // binding gets its own function below so it can have its own DB infra.
383
353
  result.functions[integrationName] = {
384
354
  handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${integrationName}.handler`,
385
355
  skipEsbuild: true, // Nested exports in node_modules - skip esbuild bundling
386
356
  package: functionPackageConfig,
387
357
  ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }), // HTTP handlers need Prisma for integration queries
388
358
  events: [
389
- ...extensionRouteEvents,
390
359
  {
391
360
  httpApi: {
392
361
  path: `/api/${integrationName}-integration/{proxy+}`,
@@ -397,6 +366,47 @@ class IntegrationBuilder extends InfrastructureBuilder {
397
366
  };
398
367
  console.log(` ✓ HTTP handler function defined`);
399
368
 
369
+ // Tier 3 Integration Extensions — one dedicated function per binding,
370
+ // namespaced under /{bindingKey} so multiple modules' extensions (e.g.
371
+ // hubspot + clockwork webhooks) never collide on the same path. The
372
+ // Prisma layer is attached only when the extension/binding declares
373
+ // useDatabase: true (default false → DB-free receiver, faster cold
374
+ // start). Route shape mirrors core's getExtensionRoutes; read directly
375
+ // to avoid coupling the build-time generator to a core runtime import.
376
+ const sanitizeBindingKey = (name) =>
377
+ String(name).replace(/[^A-Za-z0-9]/g, '');
378
+ const extensionEntries = Object.entries(
379
+ integration.Definition.extensions || {}
380
+ );
381
+ for (const [bindingKey, binding] of extensionEntries) {
382
+ const extension = binding && binding.extension;
383
+ const routes = (extension && extension.routes) || [];
384
+ if (routes.length === 0) continue;
385
+ const useDatabase =
386
+ binding.useDatabase ??
387
+ (extension && extension.useDatabase) ??
388
+ false;
389
+ const fnName = `${integrationName}__${sanitizeBindingKey(
390
+ bindingKey
391
+ )}`;
392
+ result.functions[fnName] = {
393
+ handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${fnName}.handler`,
394
+ skipEsbuild: true,
395
+ package: functionPackageConfig,
396
+ ...(usePrismaLayer &&
397
+ useDatabase && { layers: [{ Ref: 'PrismaLambdaLayer' }] }),
398
+ events: routes.map((route) => ({
399
+ httpApi: {
400
+ path: `/api/${integrationName}-integration/${bindingKey}${route.path}`,
401
+ method: route.method,
402
+ },
403
+ })),
404
+ };
405
+ console.log(
406
+ ` ✓ Extension handler function defined: ${fnName} (useDatabase: ${useDatabase})`
407
+ );
408
+ }
409
+
400
410
  // Create Queue Worker function
401
411
  const queueWorkerName = `${integrationName}QueueWorker`;
402
412
  result.functions[queueWorkerName] = {
@@ -568,7 +568,7 @@ describe('IntegrationBuilder', () => {
568
568
  ]);
569
569
  });
570
570
 
571
- it('should emit dedicated httpApi events for Tier 3 extension routes, before the catch-all', async () => {
571
+ it('emits a dedicated per-binding function (namespaced) for Tier 3 extension routes; the main handler keeps only {proxy+}', async () => {
572
572
  const appDefinition = {
573
573
  integrations: [
574
574
  {
@@ -602,15 +602,24 @@ describe('IntegrationBuilder', () => {
602
602
 
603
603
  const result = await integrationBuilder.build(appDefinition, {});
604
604
 
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([
605
+ // Dedicated per-binding function, namespaced under the binding key,
606
+ // pointing at its own handler export.
607
+ const fn = result.functions.hubspot__hubspotWebhooks;
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([
608
613
  {
609
614
  httpApi: {
610
- path: '/api/hubspot-integration/webhooks',
615
+ path: '/api/hubspot-integration/hubspotWebhooks/webhooks',
611
616
  method: 'POST',
612
617
  },
613
618
  },
619
+ ]);
620
+
621
+ // The main integration handler keeps only the catch-all.
622
+ expect(result.functions.hubspot.events).toEqual([
614
623
  {
615
624
  httpApi: {
616
625
  path: '/api/hubspot-integration/{proxy+}',
@@ -618,6 +627,45 @@ describe('IntegrationBuilder', () => {
618
627
  },
619
628
  },
620
629
  ]);
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();
621
669
  });
622
670
 
623
671
  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.590.c1af198.0",
4
+ "version": "2.0.0--canary.596.6355e72.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.590.c1af198.0",
29
- "@friggframework/schemas": "2.0.0--canary.590.c1af198.0",
30
- "@friggframework/test": "2.0.0--canary.590.c1af198.0",
28
+ "@friggframework/core": "2.0.0--canary.596.6355e72.0",
29
+ "@friggframework/schemas": "2.0.0--canary.596.6355e72.0",
30
+ "@friggframework/test": "2.0.0--canary.596.6355e72.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.590.c1af198.0",
59
- "@friggframework/prettier-config": "2.0.0--canary.590.c1af198.0",
58
+ "@friggframework/eslint-config": "2.0.0--canary.596.6355e72.0",
59
+ "@friggframework/prettier-config": "2.0.0--canary.596.6355e72.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": "c1af198e54a9e8986a69a91653b6134b9f26251c"
91
+ "gitHead": "6355e7230ef1f29f10e5336c88d4ddd257dffe18"
92
92
  }