(
input: CreateConnectionInput,
)
| 3540 | // ------------------------------------------------------------------ |
| 3541 | |
| 3542 | const connectionsCreate = ( |
| 3543 | input: CreateConnectionInput, |
| 3544 | ): Effect.Effect< |
| 3545 | Connection, |
| 3546 | | IntegrationNotFoundError |
| 3547 | | ConnectionAlreadyExistsError |
| 3548 | | CredentialProviderNotRegisteredError |
| 3549 | | InvalidConnectionInputError |
| 3550 | | StorageFailure |
| 3551 | > => |
| 3552 | Effect.gen(function* () { |
| 3553 | const name = connectionIdentifier(String(input.name)); |
| 3554 | // Typed (not StorageError) so the HTTP edge can answer 400 with the |
| 3555 | // reason instead of an opaque 500 — callers can act on it. |
| 3556 | if (input.owner === "user" && subject == null) { |
| 3557 | return yield* new InvalidConnectionInputError({ |
| 3558 | message: |
| 3559 | 'Cannot create a personal connection: this context has no user subject. Create it with owner "org", or connect as a signed-in user.', |
| 3560 | }); |
| 3561 | } |
| 3562 | const integrationRow = yield* findIntegrationRow(input.integration); |
| 3563 | if (!integrationRow) { |
| 3564 | return yield* new IntegrationNotFoundError({ |
| 3565 | slug: input.integration, |
| 3566 | }); |
| 3567 | } |
| 3568 | |
| 3569 | // Create is never a replace. This early check answers the common case |
| 3570 | // with a typed 409 before any other work, but it is NOT the guard |
| 3571 | // against concurrent creates — the row insert below is: the |
| 3572 | // transaction re-checks, the primary key breaks the tie, and the |
| 3573 | // provider write happens only after the insert wins. |
| 3574 | const duplicate = yield* findConnectionRow({ |
| 3575 | owner: input.owner, |
| 3576 | integration: input.integration, |
| 3577 | name, |
| 3578 | }); |
| 3579 | if (duplicate) { |
| 3580 | return yield* new ConnectionAlreadyExistsError({ |
| 3581 | owner: input.owner, |
| 3582 | integration: input.integration, |
| 3583 | name, |
| 3584 | }); |
| 3585 | } |
| 3586 | |
| 3587 | // Resolve the value origin(s) → one provider + an item_ids map (one entry |
| 3588 | // per named input). All of a connection's inputs share a single provider: |
| 3589 | // pasted inputs go to the default writable store, external `from` inputs to |
| 3590 | // their provider. Mixing pasted + external, or two external providers, is |
| 3591 | // rejected (the connection row carries one `provider`). |
| 3592 | const inputs = normalizeConnectionInputs(input); |
| 3593 | const pasted = inputs.filter((i) => "value" in i.origin); |
| 3594 | const external = inputs.filter((i) => "from" in i.origin); |
| 3595 | // A credentialed connection is born wired: it must reference at least |
| 3596 | // one credential input. An empty binding (no inputs at all — e.g. an |
| 3597 | // empty `values`/`inputs` map) is a credential with no credential: it |
| 3598 | // would persist, produce a full tool catalog, and then fail every |
| 3599 | // invocation with `connection_value_missing`. Reject it here — EXCEPT |
no test coverage detected