@friggframework/devtools 2.0.0--canary.596.6355e72.0 → 2.0.0--canary.596.fc8739c.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.
@@ -348,8 +348,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
348
348
  }
349
349
 
350
350
  // Create HTTP API handler for integration (catch-all route AFTER
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.
351
+ // webhooks). Extension routes get their own functions below.
353
352
  result.functions[integrationName] = {
354
353
  handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${integrationName}.handler`,
355
354
  skipEsbuild: true, // Nested exports in node_modules - skip esbuild bundling
@@ -366,13 +365,8 @@ class IntegrationBuilder extends InfrastructureBuilder {
366
365
  };
367
366
  console.log(` ✓ HTTP handler function defined`);
368
367
 
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.
368
+ // One serverless function per extension binding, namespaced under
369
+ // /{bindingKey}. Prisma layer attached only when useDatabase is true.
376
370
  const sanitizeBindingKey = (name) =>
377
371
  String(name).replace(/[^A-Za-z0-9]/g, '');
378
372
  const extensionEntries = Object.entries(
@@ -386,9 +380,21 @@ class IntegrationBuilder extends InfrastructureBuilder {
386
380
  binding.useDatabase ??
387
381
  (extension && extension.useDatabase) ??
388
382
  false;
383
+ // Wire contract: core's integration-defined-routers derives the
384
+ // identical handler key. Keep both in sync.
389
385
  const fnName = `${integrationName}__${sanitizeBindingKey(
390
386
  bindingKey
391
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
+ }
392
398
  result.functions[fnName] = {
393
399
  handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${fnName}.handler`,
394
400
  skipEsbuild: true,
@@ -668,6 +668,30 @@ describe('IntegrationBuilder', () => {
668
668
  expect(withoutDb.functions.hs__wh.layers).toBeUndefined();
669
669
  });
670
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
+ });
694
+
671
695
  it('should only have the catch-all proxy route when no extensions are declared', async () => {
672
696
  const appDefinition = {
673
697
  integrations: [{ Definition: { name: 'plain' } }],
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.596.6355e72.0",
4
+ "version": "2.0.0--canary.596.fc8739c.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.596.6355e72.0",
29
- "@friggframework/schemas": "2.0.0--canary.596.6355e72.0",
30
- "@friggframework/test": "2.0.0--canary.596.6355e72.0",
28
+ "@friggframework/core": "2.0.0--canary.596.fc8739c.0",
29
+ "@friggframework/schemas": "2.0.0--canary.596.fc8739c.0",
30
+ "@friggframework/test": "2.0.0--canary.596.fc8739c.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.596.6355e72.0",
59
- "@friggframework/prettier-config": "2.0.0--canary.596.6355e72.0",
58
+ "@friggframework/eslint-config": "2.0.0--canary.596.fc8739c.0",
59
+ "@friggframework/prettier-config": "2.0.0--canary.596.fc8739c.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": "6355e7230ef1f29f10e5336c88d4ddd257dffe18"
91
+ "gitHead": "fc8739c1c49a9731b8465e0d5e864c71db20c581"
92
92
  }