@fragno-dev/project-connector-fragment 0.0.0-stage → 0.0.1
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/LICENSE.md +16 -0
- package/README.md +285 -2
- package/bin/run.js +4 -0
- package/dist/browser/client/react.d.ts +174 -0
- package/dist/browser/client/react.d.ts.map +1 -0
- package/dist/browser/client/react.js +195 -0
- package/dist/browser/client/react.js.map +1 -0
- package/dist/browser/client/solid.d.ts +177 -0
- package/dist/browser/client/solid.d.ts.map +1 -0
- package/dist/browser/client/solid.js +130 -0
- package/dist/browser/client/solid.js.map +1 -0
- package/dist/browser/client/svelte.d.ts +177 -0
- package/dist/browser/client/svelte.d.ts.map +1 -0
- package/dist/browser/client/svelte.js +132 -0
- package/dist/browser/client/svelte.js.map +1 -0
- package/dist/browser/client/vanilla.d.ts +204 -0
- package/dist/browser/client/vanilla.d.ts.map +1 -0
- package/dist/browser/client/vanilla.js +160 -0
- package/dist/browser/client/vanilla.js.map +1 -0
- package/dist/browser/client/vue.d.ts +177 -0
- package/dist/browser/client/vue.d.ts.map +1 -0
- package/dist/browser/client/vue.js +128 -0
- package/dist/browser/client/vue.js.map +1 -0
- package/dist/browser/client-BCzNDSlW.d.ts +711 -0
- package/dist/browser/client-BCzNDSlW.d.ts.map +1 -0
- package/dist/browser/index.d.ts +504 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/src-CSPQuoKz.js +2286 -0
- package/dist/browser/src-CSPQuoKz.js.map +1 -0
- package/dist/cli/authorization-browser.js +20 -0
- package/dist/cli/authorization-browser.js.map +1 -0
- package/dist/cli/cli.d.ts +6 -0
- package/dist/cli/cli.d.ts.map +1 -0
- package/dist/cli/cli.js +258 -0
- package/dist/cli/cli.js.map +1 -0
- package/dist/cli/definition.js +13 -0
- package/dist/cli/definition.js.map +1 -0
- package/dist/cli/index.js +14 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/project-connector-client.js +235 -0
- package/dist/cli/project-connector-client.js.map +1 -0
- package/dist/cli/project-connector-contracts.js +125 -0
- package/dist/cli/project-connector-contracts.js.map +1 -0
- package/dist/cli/routes.js +449 -0
- package/dist/cli/routes.js.map +1 -0
- package/dist/cli/schema.js +18 -0
- package/dist/cli/schema.js.map +1 -0
- package/dist/node/definition.d.ts +170 -0
- package/dist/node/definition.d.ts.map +1 -0
- package/dist/node/definition.js +13 -0
- package/dist/node/definition.js.map +1 -0
- package/dist/node/index.d.ts +456 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +36 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/project-connector-client.d.ts +13 -0
- package/dist/node/project-connector-client.d.ts.map +1 -0
- package/dist/node/project-connector-client.js +235 -0
- package/dist/node/project-connector-client.js.map +1 -0
- package/dist/node/project-connector-contracts.d.ts +179 -0
- package/dist/node/project-connector-contracts.d.ts.map +1 -0
- package/dist/node/project-connector-contracts.js +125 -0
- package/dist/node/project-connector-contracts.js.map +1 -0
- package/dist/node/routes.d.ts +307 -0
- package/dist/node/routes.d.ts.map +1 -0
- package/dist/node/routes.js +449 -0
- package/dist/node/routes.js.map +1 -0
- package/dist/node/schema.d.ts +49 -0
- package/dist/node/schema.d.ts.map +1 -0
- package/dist/node/schema.js +18 -0
- package/dist/node/schema.js.map +1 -0
- package/dist/node/testing/project-connector-test-gateway.d.ts +63 -0
- package/dist/node/testing/project-connector-test-gateway.d.ts.map +1 -0
- package/dist/node/testing/project-connector-test-gateway.js +370 -0
- package/dist/node/testing/project-connector-test-gateway.js.map +1 -0
- package/package.json +111 -3
package/LICENSE.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Copyright 2025 - present "ReJot Nederland B.V.", and individual contributors.
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
4
|
+
associated documentation files (the “Software”), to deal in the Software without restriction,
|
|
5
|
+
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|
6
|
+
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
|
7
|
+
furnished to do so, subject to the following conditions:
|
|
8
|
+
|
|
9
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
10
|
+
portions of the Software.
|
|
11
|
+
|
|
12
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
|
13
|
+
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
14
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
|
15
|
+
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
16
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,286 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @fragno-dev/project-connector-fragment
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A DB-backed Fragno fragment for the hosted OOMOL **Project Connector** API. It connects product
|
|
4
|
+
users to Gmail and other configured OAuth providers, verifies account bindings, reads provider
|
|
5
|
+
profiles, and executes actions using an explicit connected account.
|
|
6
|
+
|
|
7
|
+
Account operations use `oo_proj_...` project keys. Authoritative action contracts use a separate
|
|
8
|
+
server-only catalog credential through the SDK's `Connector.catalog` surface; it is never used to
|
|
9
|
+
execute actions or connect users. Create a project and provider configuration in OOMOL Console
|
|
10
|
+
first; see the [Project Connector guide](https://oomol.com/docs/project-connector/).
|
|
11
|
+
|
|
12
|
+
## Try the CLI
|
|
13
|
+
|
|
14
|
+
From the repository root, using the same Node version used to install `better-sqlite3`:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm --filter @fragno-dev/project-connector-fragment cli -- check \
|
|
18
|
+
--env-file ../../apps/backoffice/.dev.vars
|
|
19
|
+
|
|
20
|
+
pnpm --filter @fragno-dev/project-connector-fragment cli -- connect gmail \
|
|
21
|
+
--env-file ../../apps/backoffice/.dev.vars \
|
|
22
|
+
--user-id wilco --connection-name work --beep
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The environment file is relative to the package directory for these `pnpm` commands. With the built
|
|
26
|
+
package, commands can also run from the repository root:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
node packages/project-connector-fragment/bin/run.js check --env-file apps/backoffice/.dev.vars
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
After installation, use `fragno-project-connector` instead of the `node .../bin/run.js` prefix.
|
|
33
|
+
|
|
34
|
+
The required server environment variables are:
|
|
35
|
+
|
|
36
|
+
```dotenv
|
|
37
|
+
OOMOL_CONNECTOR_BASE_URL=https://connector.oomol.com/v1
|
|
38
|
+
OOMOL_PROJECT_API_KEY=oo_proj_...
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`OOMOL_CONNECTOR_BASE_URL` is the Connector API root and must end in `/v1`. Environment variables
|
|
42
|
+
override values in `--env-file`. The key never travels through the browser and is not stored in
|
|
43
|
+
SQLite or printed in diagnostics.
|
|
44
|
+
|
|
45
|
+
Action discovery additionally requires `OOMOL_CONNECTOR_CATALOG_API_KEY`, a separate credential with
|
|
46
|
+
catalog access on the same gateway (for hosted OOMOL, a personal `api_...` key). Project credentials
|
|
47
|
+
are never substituted for catalog credentials. Omit it to leave account operations available but
|
|
48
|
+
fail action discovery with `catalog_not_configured` when the selected configuration allows actions.
|
|
49
|
+
A known configuration with an empty allowlist still returns an empty action list without catalog
|
|
50
|
+
access.
|
|
51
|
+
|
|
52
|
+
`connect` opens the authorization link, temporarily listens on `127.0.0.1:3930`, and polls the saved
|
|
53
|
+
request. On success it stores the verified binding and performs a **read-only provider profile
|
|
54
|
+
check**. It does not read Gmail messages or send email. `--beep` requests an audible authorization
|
|
55
|
+
prompt; `--no-open` only prints the link. `--provider-config-id` selects a particular provider
|
|
56
|
+
config instead of asking the gateway to resolve the service.
|
|
57
|
+
|
|
58
|
+
Local state defaults to `~/.fragno/project-connector-fragment`; override it with `--data-dir`. Keep
|
|
59
|
+
`--user-id` and `--data-dir` consistent across commands. Use `--port` for a different callback port
|
|
60
|
+
and `--timeout-ms` to shorten the OAuth wait (maximum ten minutes).
|
|
61
|
+
|
|
62
|
+
Additional commands:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
fragno-project-connector accounts --user-id wilco --env-file ./connector.env
|
|
66
|
+
fragno-project-connector status <request-id> --user-id wilco --env-file ./connector.env
|
|
67
|
+
fragno-project-connector profile <account-id> --user-id wilco --env-file ./connector.env
|
|
68
|
+
|
|
69
|
+
# Explicit action calls may read or modify provider data. Nothing executes automatically.
|
|
70
|
+
fragno-project-connector execute <account-id> gmail.search_threads \
|
|
71
|
+
--user-id wilco --env-file ./connector.env --input '{"query":"is:unread"}'
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
All CLI operations exercise the fragment's real HTTP handler and SQLite migrations. The callback
|
|
75
|
+
page is navigation feedback only: even a forged callback account ID cannot create a binding. The
|
|
76
|
+
local CLI chooses its own user identity; do not expose it as an application server.
|
|
77
|
+
|
|
78
|
+
A successful `check` establishes gateway reachability and project-key authentication, not Gmail
|
|
79
|
+
availability. The SaaS API has no health endpoint, so this check expects the authenticated
|
|
80
|
+
`connection_request_not_found` response for a fresh, nonexistent request ID. All provider calls go
|
|
81
|
+
through the official `@oomol-lab/connector` `ProjectConnector` client; action metadata uses its
|
|
82
|
+
separate `Connector.catalog` surface.
|
|
83
|
+
|
|
84
|
+
## Server integration
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { createProjectConnectorFragment } from "@fragno-dev/project-connector-fragment";
|
|
88
|
+
|
|
89
|
+
const callbackUrl = "https://app.example.com/integrations/connected";
|
|
90
|
+
const fragment = createProjectConnectorFragment(
|
|
91
|
+
{
|
|
92
|
+
baseUrl: env.OOMOL_CONNECTOR_BASE_URL,
|
|
93
|
+
apiKey: env.OOMOL_PROJECT_API_KEY,
|
|
94
|
+
catalogApiKey: env.OOMOL_CONNECTOR_CATALOG_API_KEY ?? null,
|
|
95
|
+
// Implement this using your application's authenticated session, not a user-supplied ID.
|
|
96
|
+
getExternalUserId: resolveAuthenticatedUserId,
|
|
97
|
+
allowedReturnUrls: (url) => url.toString() === callbackUrl,
|
|
98
|
+
},
|
|
99
|
+
{ databaseAdapter },
|
|
100
|
+
);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`getExternalUserId(headers)` returns the authenticated product user's external ID or `null`. Mount
|
|
104
|
+
and migrate the fragment using your application's usual Fragno integration. The default mount is
|
|
105
|
+
`/api/project-connector-fragment`. Restrict provider/action access with your application's
|
|
106
|
+
authorization policy when needed; the fragment enforces account ownership and action/service
|
|
107
|
+
matching.
|
|
108
|
+
|
|
109
|
+
## Provider configuration discovery
|
|
110
|
+
|
|
111
|
+
`GET /provider-configs` returns live OAuth configuration metadata for the configured project:
|
|
112
|
+
`projectId` and `providerConfigs`, whose entries contain `id`, `service`, `displayName`,
|
|
113
|
+
`callbackUrl`, `effectiveScopes`, and `proxyAvailable`, without action IDs. Use the
|
|
114
|
+
`useProviderConfigs` client hook to load this catalog, then pass the selected entry's `id` as
|
|
115
|
+
`providerConfigId` when connecting, especially when several configurations use the same service.
|
|
116
|
+
|
|
117
|
+
`GET /provider-configs/:providerConfigId/actions` and the `useProviderActions` client hook retrieve
|
|
118
|
+
`{ projectId, providerConfigId, actions }` for one exact configuration. Each action has `id`,
|
|
119
|
+
`service`, `name`, nullable `description`, and authoritative `inputSchema`/`outputSchema` JSON
|
|
120
|
+
Schemas. An unknown configuration returns `PROVIDER_CONFIG_NOT_FOUND` (404); a known configuration
|
|
121
|
+
with no allowed actions returns an empty `actions` list. Discovery never executes actions.
|
|
122
|
+
|
|
123
|
+
Discovery requires an authenticated product user but is project-scoped, not an account list. It
|
|
124
|
+
neither creates connections nor verifies provider accounts, and does not include API-key or custom
|
|
125
|
+
credential configurations. The server calls `/v1/saas/oauth/provider-configs` with the project key;
|
|
126
|
+
then retrieves the selected service's catalog in one `/v1/actions?service=...` request through
|
|
127
|
+
`Connector.catalog.actions` using only the separate catalog credential. Definitions are indexed by
|
|
128
|
+
unique action ID and selected in the configuration's allowlist order; duplicate allowlist IDs are
|
|
129
|
+
returned once. Only allowed definitions are returned. Their service must match the configuration,
|
|
130
|
+
and both schemas must be present; missing or invalid contracts fail the entire discovery response.
|
|
131
|
+
Catalog presence never expands the configuration's allowlist or bypasses account ownership/execution
|
|
132
|
+
authorization. Credentials and extra upstream fields stay outside the public response. Unsupported
|
|
133
|
+
deployments and upstream failures return `PROJECT_CONNECTOR_ERROR`, rather than an invented,
|
|
134
|
+
partial, or cached catalog.
|
|
135
|
+
|
|
136
|
+
## Named connections and resumable OAuth
|
|
137
|
+
|
|
138
|
+
A named connection is the exact tuple `{ projectId, providerConfigId, connectionName }` within the
|
|
139
|
+
user identity established by `getExternalUserId`. Use the project/configuration IDs from discovery;
|
|
140
|
+
connection names follow the gateway's alias rule: lowercase letters, digits, underscores, and
|
|
141
|
+
hyphens, starting with a letter or digit. Every request and account has a name.
|
|
142
|
+
|
|
143
|
+
- `GET /connection-requests/by-name?projectId=...&providerConfigId=...&connectionName=...` returns
|
|
144
|
+
the single matching saved OAuth request as `{ request }`, with `request: null` when absent.
|
|
145
|
+
- `GET /accounts/by-name` with the same query returns `{ account }`, with `account: null` when
|
|
146
|
+
absent.
|
|
147
|
+
|
|
148
|
+
Both reads use indexed local state. They do not contact the gateway, refresh consent, execute
|
|
149
|
+
actions, or establish provider health. An OAuth request and its confirmed account have separate
|
|
150
|
+
meanings: a replacement authorization may be pending while the previously confirmed account remains
|
|
151
|
+
available.
|
|
152
|
+
|
|
153
|
+
Named lookup is additive: `POST /connection-requests` still starts a fresh attempt on every call,
|
|
154
|
+
including repeated or concurrent calls with the same name. Earlier request IDs remain usable, and
|
|
155
|
+
confirmed accounts remain addressable by their account IDs. Refreshing a new account does not remove
|
|
156
|
+
other accounts sharing its name. Reauthorization returning the same owned account ID updates that
|
|
157
|
+
binding without duplicating it; an incoming ID belonging to another user cannot replace their
|
|
158
|
+
binding.
|
|
159
|
+
|
|
160
|
+
If either lookup matches more than one record, it returns `CONNECTION_AMBIGUOUS` (409), even when
|
|
161
|
+
only one request is pending. The fragment does not choose the newest record, prefer a status, or
|
|
162
|
+
infer a current attempt. Lookups inspect at most two matching records plus the pagination lookahead;
|
|
163
|
+
no historical reconciliation or current-selection store is introduced. Use an already-known request
|
|
164
|
+
or account ID when a name is ambiguous.
|
|
165
|
+
|
|
166
|
+
After a page reload or server restart, recover the source-owned request instead of keeping a
|
|
167
|
+
separate setup handle:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
const selector = {
|
|
171
|
+
projectId: "your-project-id",
|
|
172
|
+
providerConfigId: "your-gmail-provider-config-id",
|
|
173
|
+
connectionName: "work",
|
|
174
|
+
};
|
|
175
|
+
const query = new URLSearchParams(selector);
|
|
176
|
+
const response = await fetch(
|
|
177
|
+
`/api/project-connector-fragment/connection-requests/by-name?${query}`,
|
|
178
|
+
);
|
|
179
|
+
if (!response.ok) throw new Error("Could not read the named authorization request");
|
|
180
|
+
const { request: pending } = await response.json();
|
|
181
|
+
if (pending) {
|
|
182
|
+
await connector.refreshConnection().mutate({ path: { requestId: pending.id } });
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`useNamedConnectionRequest` and `useNamedAccount` provide the same reads as client stores. Creating
|
|
187
|
+
an OAuth request invalidates named request stores; refreshing invalidates named request, named
|
|
188
|
+
account, and account-list stores. Missing/empty selector fields return `INVALID_SELECTOR` (400).
|
|
189
|
+
User identity is never accepted in the selector; another user's records remain absent.
|
|
190
|
+
|
|
191
|
+
The gateway alias is not an idempotency key. Each start creates its own remote authorization link;
|
|
192
|
+
link creation happens outside the retried local save. A lost gateway response or failed local save
|
|
193
|
+
is not claimed to be recoverable by alias, and this fragment does not introduce remote cleanup
|
|
194
|
+
machinery. Repeating a start is not a way to resume an existing attempt.
|
|
195
|
+
|
|
196
|
+
**Schema migration:** version 4 adds non-unique named lookup indexes; databases keep duplicate names
|
|
197
|
+
without reconciliation or a reset. Version 6 makes `connectionName` non-null on requests and
|
|
198
|
+
accounts. Every start has required a name, so stored rows are expected to have one; the migration
|
|
199
|
+
fails on a database that holds a null name.
|
|
200
|
+
|
|
201
|
+
### Readiness hook
|
|
202
|
+
|
|
203
|
+
`onConnectionReadinessChanged` is an optional durable hook in the fragment config. It receives
|
|
204
|
+
`{ externalUserId, service, connection, ready }`, where `connection` is the named selector, when a
|
|
205
|
+
write changes whether the name has a confirmed account: `ready: false` when the first request for a
|
|
206
|
+
name starts, and `ready: true` when a refresh confirms the name's first account. A refresh that
|
|
207
|
+
moves an existing account ID to another name also reports the old name `ready: false` when it has no
|
|
208
|
+
other account. Further attempts for a name that already has an account fire nothing. The gateway
|
|
209
|
+
does not notify the fragment of consent or revocation, so readiness changes only when
|
|
210
|
+
`POST /connection-requests/:requestId/refresh` runs.
|
|
211
|
+
|
|
212
|
+
## Client flow
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
import { createProjectConnectorFragmentClient } from "@fragno-dev/project-connector-fragment/vanilla";
|
|
216
|
+
|
|
217
|
+
const connector = createProjectConnectorFragmentClient();
|
|
218
|
+
const request = await connector.connect().mutate({
|
|
219
|
+
body: {
|
|
220
|
+
providerConfigId: "your-gmail-provider-config-id",
|
|
221
|
+
connectionName: "work",
|
|
222
|
+
returnUri: "https://app.example.com/integrations/connected",
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Remember request.id before navigating. Do not trust callback query parameters.
|
|
227
|
+
window.location.assign(request.authorizationUrl);
|
|
228
|
+
|
|
229
|
+
// After returning, or while polling in the initiating UI:
|
|
230
|
+
const confirmed = await connector.refreshConnection().mutate({ path: { requestId: request.id } });
|
|
231
|
+
if (confirmed.state.status === "connected") {
|
|
232
|
+
const profile = await fetch(
|
|
233
|
+
`/api/project-connector-fragment/accounts/${confirmed.state.connectedAccountId}/profile`,
|
|
234
|
+
).then((response) => response.json());
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Use exactly one of `providerConfigId` or `service` when connecting. Explicit provider IDs are
|
|
239
|
+
preferred for projects with multiple configurations for one service. React, Vue, Svelte, and Solid
|
|
240
|
+
entrypoints also export `createProjectConnectorFragmentClient` with their native adapter
|
|
241
|
+
conventions.
|
|
242
|
+
|
|
243
|
+
Client operations: `useProviderConfigs`, `useProviderActions`, `useStatus`, `useAccounts`,
|
|
244
|
+
`useNamedAccount`, `useNamedConnectionRequest`, `useProfile`, `connect`, `refreshConnection`, and
|
|
245
|
+
`executeAction`. Refreshing a connection invalidates account and named-request stores. The account
|
|
246
|
+
list is cursor-paginated (25 items per page); it lists local verified bindings, not live provider
|
|
247
|
+
availability.
|
|
248
|
+
|
|
249
|
+
## Routes
|
|
250
|
+
|
|
251
|
+
| Method | Path | Purpose |
|
|
252
|
+
| ------ | ------------------------------------------------------------------------------------ | --------------------------------------------------------------- |
|
|
253
|
+
| GET | `/provider-configs` | Overview of this project's available OAuth configurations |
|
|
254
|
+
| GET | `/provider-configs/:providerConfigId/actions` | List allowed action contracts for one exact OAuth configuration |
|
|
255
|
+
| GET | `/status` | Check project-key authentication |
|
|
256
|
+
| GET | `/connection-requests/by-name?projectId=...&providerConfigId=...&connectionName=...` | Read an unambiguous request by exact name |
|
|
257
|
+
| POST | `/connection-requests` | Start a fresh OAuth request |
|
|
258
|
+
| POST | `/connection-requests/:requestId/refresh` | Verify gateway status and bind a completed account |
|
|
259
|
+
| GET | `/accounts?cursor=...` | List this user's locally verified account bindings |
|
|
260
|
+
| GET | `/accounts/by-name?projectId=...&providerConfigId=...&connectionName=...` | Read an unambiguous confirmed account by exact name |
|
|
261
|
+
| GET | `/accounts/:accountId/profile` | Read and verify the provider account profile |
|
|
262
|
+
| POST | `/accounts/:accountId/actions/:actionId` | Execute `{ "input": { ... } }` using the saved selector |
|
|
263
|
+
|
|
264
|
+
A connection is bound only when the authenticated gateway response is `connected` and matches the
|
|
265
|
+
saved request ID, project, provider config, external user, service, and connection name. `failed`
|
|
266
|
+
and `expired` are terminal and never create account bindings. Product users cannot select a
|
|
267
|
+
different user ID in request bodies, read other users' bindings, or execute actions on them.
|
|
268
|
+
|
|
269
|
+
Action output stays opaque; responses preserve `executionId` and `actionId` for troubleshooting.
|
|
270
|
+
Requests have a 30-second timeout. Action POSTs are **not automatically retried**, since retrying a
|
|
271
|
+
write such as `gmail.send_email` can duplicate its side effects. Upstream errors return safe
|
|
272
|
+
connector codes without exposing raw response bodies. Database errors still surface as database
|
|
273
|
+
errors.
|
|
274
|
+
|
|
275
|
+
This first version deliberately excludes provider setup/admin APIs, credential import, account
|
|
276
|
+
disconnect, a personal catalog API, and background synchronization. OAuth tokens are managed by
|
|
277
|
+
Project Connector, not by this fragment.
|
|
278
|
+
|
|
279
|
+
## Development
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
pnpm exec turbo build types:check test --filter=@fragno-dev/project-connector-fragment --output-logs=errors-only
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Scenario tests exercise a real local HTTP gateway, SQLite operations, authenticated routes, client
|
|
286
|
+
store invalidation, cursor pagination, and persistent CLI state. They do not use your real API key.
|
package/bin/run.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { a as FragnoClientRequestError, c as InferOr, d as MaybeExtractPathParamsOrWiden, f as QueryParamsHint, i as ReadableAtom, l as ExtractPathParamsOrWiden, o as NonGetHTTPMethod, r as FetcherValue, s as FragnoPublicClientConfig, u as HasPathParams } from "../client-BCzNDSlW.js";
|
|
2
|
+
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
3
|
+
//#region ../fragno/dist/client/react.d.ts
|
|
4
|
+
//#region src/client/react.d.ts
|
|
5
|
+
type FragnoReactHook<_TMethod extends "GET", TPath extends string, TOutputSchema extends StandardSchemaV1, TErrorCode extends string, TQueryParameters extends string> = (args?: {
|
|
6
|
+
path?: MaybeExtractPathParamsOrWiden<TPath, string | ReadableAtom<string>>;
|
|
7
|
+
query?: QueryParamsHint<TQueryParameters, string | ReadableAtom<string>>;
|
|
8
|
+
}) => FetcherValue<StandardSchemaV1.InferOutput<TOutputSchema>, FragnoClientRequestError<NonNullable<TErrorCode>>> & {
|
|
9
|
+
refetch: () => void;
|
|
10
|
+
};
|
|
11
|
+
type FragnoReactMutator<_TMethod extends NonGetHTTPMethod, TPath extends string, TInputSchema extends StandardSchemaV1 | undefined, TOutputSchema extends StandardSchemaV1 | undefined, TErrorCode extends string, TQueryParameters extends string> = () => {
|
|
12
|
+
mutate: ({ body, path, query }: {
|
|
13
|
+
body?: InferOr<TInputSchema, undefined>;
|
|
14
|
+
path?: HasPathParams<TPath> extends true ? ExtractPathParamsOrWiden<TPath, string | ReadableAtom<string>> : undefined;
|
|
15
|
+
query?: QueryParamsHint<TQueryParameters, string | ReadableAtom<string>>;
|
|
16
|
+
}) => Promise<InferOr<TOutputSchema, undefined>>;
|
|
17
|
+
loading?: boolean | undefined;
|
|
18
|
+
error?: FragnoClientRequestError<NonNullable<TErrorCode>[number]> | undefined;
|
|
19
|
+
data?: InferOr<TOutputSchema, undefined> | undefined;
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/client/react.d.ts
|
|
23
|
+
/** Creates the React client for the ProjectConnector fragment. */
|
|
24
|
+
declare function createProjectConnectorFragmentClient(config?: FragnoPublicClientConfig): {
|
|
25
|
+
useProviderConfigs: FragnoReactHook<"GET", "/provider-configs", import("zod").ZodObject<{
|
|
26
|
+
projectId: import("zod").ZodString;
|
|
27
|
+
providerConfigs: import("zod").ZodArray<import("zod").ZodObject<{
|
|
28
|
+
id: import("zod").ZodString;
|
|
29
|
+
service: import("zod").ZodString;
|
|
30
|
+
displayName: import("zod").ZodString;
|
|
31
|
+
callbackUrl: import("zod").ZodURL;
|
|
32
|
+
effectiveScopes: import("zod").ZodArray<import("zod").ZodString>;
|
|
33
|
+
proxyAvailable: import("zod").ZodBoolean;
|
|
34
|
+
}, import("zod/v4/core").$strip>>;
|
|
35
|
+
}, import("zod/v4/core").$strip>, "UNAUTHENTICATED" | "PROJECT_CONNECTOR_ERROR", string>;
|
|
36
|
+
useProviderActions: FragnoReactHook<"GET", "/provider-configs/:providerConfigId/actions", import("zod").ZodObject<{
|
|
37
|
+
projectId: import("zod").ZodString;
|
|
38
|
+
providerConfigId: import("zod").ZodString;
|
|
39
|
+
actions: import("zod").ZodArray<import("zod").ZodObject<{
|
|
40
|
+
id: import("zod").ZodString;
|
|
41
|
+
service: import("zod").ZodString;
|
|
42
|
+
name: import("zod").ZodString;
|
|
43
|
+
description: import("zod").ZodNullable<import("zod").ZodString>;
|
|
44
|
+
inputSchema: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>;
|
|
45
|
+
outputSchema: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>;
|
|
46
|
+
}, import("zod/v4/core").$strip>>;
|
|
47
|
+
}, import("zod/v4/core").$strip>, "UNAUTHENTICATED" | "PROJECT_CONNECTOR_ERROR" | "PROVIDER_CONFIG_NOT_FOUND", string>;
|
|
48
|
+
useStatus: FragnoReactHook<"GET", "/status", import("zod").ZodObject<{
|
|
49
|
+
authenticated: import("zod").ZodLiteral<true>;
|
|
50
|
+
}, import("zod/v4/core").$strip>, "UNAUTHENTICATED" | "PROJECT_CONNECTOR_ERROR", string>;
|
|
51
|
+
useAccounts: FragnoReactHook<"GET", "/accounts", import("zod").ZodObject<{
|
|
52
|
+
accounts: import("zod").ZodArray<import("zod").ZodObject<{
|
|
53
|
+
id: import("zod").ZodString;
|
|
54
|
+
projectId: import("zod").ZodString;
|
|
55
|
+
providerConfigId: import("zod").ZodString;
|
|
56
|
+
externalUserId: import("zod").ZodString;
|
|
57
|
+
service: import("zod").ZodString;
|
|
58
|
+
connectionName: import("zod").ZodString;
|
|
59
|
+
}, import("zod/v4/core").$strip>>;
|
|
60
|
+
cursor: import("zod").ZodNullable<import("zod").ZodString>;
|
|
61
|
+
hasNextPage: import("zod").ZodBoolean;
|
|
62
|
+
}, import("zod/v4/core").$strip>, "UNAUTHENTICATED" | "INVALID_CURSOR", "cursor">;
|
|
63
|
+
useNamedAccount: FragnoReactHook<"GET", "/accounts/by-name", import("zod").ZodObject<{
|
|
64
|
+
account: import("zod").ZodNullable<import("zod").ZodObject<{
|
|
65
|
+
id: import("zod").ZodString;
|
|
66
|
+
projectId: import("zod").ZodString;
|
|
67
|
+
providerConfigId: import("zod").ZodString;
|
|
68
|
+
externalUserId: import("zod").ZodString;
|
|
69
|
+
service: import("zod").ZodString;
|
|
70
|
+
connectionName: import("zod").ZodString;
|
|
71
|
+
}, import("zod/v4/core").$strip>>;
|
|
72
|
+
}, import("zod/v4/core").$strict>, "UNAUTHENTICATED" | "INVALID_SELECTOR" | "CONNECTION_AMBIGUOUS", "projectId" | "providerConfigId" | "connectionName">;
|
|
73
|
+
useNamedConnectionRequest: FragnoReactHook<"GET", "/connection-requests/by-name", import("zod").ZodObject<{
|
|
74
|
+
request: import("zod").ZodNullable<import("zod").ZodObject<{
|
|
75
|
+
id: import("zod").ZodString;
|
|
76
|
+
projectId: import("zod").ZodString;
|
|
77
|
+
providerConfigId: import("zod").ZodString;
|
|
78
|
+
externalUserId: import("zod").ZodString;
|
|
79
|
+
service: import("zod").ZodString;
|
|
80
|
+
connectionName: import("zod").ZodString;
|
|
81
|
+
authorizationUrl: import("zod").ZodURL;
|
|
82
|
+
expiresAt: import("zod").ZodString;
|
|
83
|
+
state: import("zod").ZodDiscriminatedUnion<[import("zod").ZodObject<{
|
|
84
|
+
status: import("zod").ZodLiteral<"initiated">;
|
|
85
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
86
|
+
status: import("zod").ZodLiteral<"connected">;
|
|
87
|
+
connectedAccountId: import("zod").ZodString;
|
|
88
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
89
|
+
status: import("zod").ZodLiteral<"failed">;
|
|
90
|
+
errorCode: import("zod").ZodNullable<import("zod").ZodString>;
|
|
91
|
+
errorMessage: import("zod").ZodNullable<import("zod").ZodString>;
|
|
92
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
93
|
+
status: import("zod").ZodLiteral<"expired">;
|
|
94
|
+
}, import("zod/v4/core").$strip>], "status">;
|
|
95
|
+
}, import("zod/v4/core").$strip>>;
|
|
96
|
+
}, import("zod/v4/core").$strict>, "UNAUTHENTICATED" | "INVALID_SELECTOR" | "CONNECTION_AMBIGUOUS", "projectId" | "providerConfigId" | "connectionName">;
|
|
97
|
+
useProfile: FragnoReactHook<"GET", "/accounts/:accountId/profile", import("zod").ZodObject<{
|
|
98
|
+
connectedAccountId: import("zod").ZodString;
|
|
99
|
+
externalUserId: import("zod").ZodString;
|
|
100
|
+
service: import("zod").ZodString;
|
|
101
|
+
profile: import("zod").ZodObject<{
|
|
102
|
+
id: import("zod").ZodString;
|
|
103
|
+
kind: import("zod").ZodString;
|
|
104
|
+
username: import("zod").ZodNullable<import("zod").ZodString>;
|
|
105
|
+
displayName: import("zod").ZodNullable<import("zod").ZodString>;
|
|
106
|
+
avatarUrl: import("zod").ZodNullable<import("zod").ZodString>;
|
|
107
|
+
email: import("zod").ZodNullable<import("zod").ZodString>;
|
|
108
|
+
metadata: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>;
|
|
109
|
+
}, import("zod/v4/core").$strip>;
|
|
110
|
+
fetchedAt: import("zod").ZodNumber;
|
|
111
|
+
}, import("zod/v4/core").$strip>, "UNAUTHENTICATED" | "PROJECT_CONNECTOR_ERROR" | "ACCOUNT_NOT_FOUND", string>;
|
|
112
|
+
connect: FragnoReactMutator<NonGetHTTPMethod, "/connection-requests", import("zod").ZodUnion<readonly [import("zod").ZodObject<{
|
|
113
|
+
service: import("zod").ZodString;
|
|
114
|
+
connectionName: import("zod").ZodString;
|
|
115
|
+
returnUri: import("zod").ZodURL;
|
|
116
|
+
}, import("zod/v4/core").$strict>, import("zod").ZodObject<{
|
|
117
|
+
providerConfigId: import("zod").ZodString;
|
|
118
|
+
connectionName: import("zod").ZodString;
|
|
119
|
+
returnUri: import("zod").ZodURL;
|
|
120
|
+
}, import("zod/v4/core").$strict>]> | undefined, import("zod").ZodObject<{
|
|
121
|
+
id: import("zod").ZodString;
|
|
122
|
+
projectId: import("zod").ZodString;
|
|
123
|
+
providerConfigId: import("zod").ZodString;
|
|
124
|
+
externalUserId: import("zod").ZodString;
|
|
125
|
+
service: import("zod").ZodString;
|
|
126
|
+
connectionName: import("zod").ZodString;
|
|
127
|
+
authorizationUrl: import("zod").ZodURL;
|
|
128
|
+
expiresAt: import("zod").ZodString;
|
|
129
|
+
state: import("zod").ZodDiscriminatedUnion<[import("zod").ZodObject<{
|
|
130
|
+
status: import("zod").ZodLiteral<"initiated">;
|
|
131
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
132
|
+
status: import("zod").ZodLiteral<"connected">;
|
|
133
|
+
connectedAccountId: import("zod").ZodString;
|
|
134
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
135
|
+
status: import("zod").ZodLiteral<"failed">;
|
|
136
|
+
errorCode: import("zod").ZodNullable<import("zod").ZodString>;
|
|
137
|
+
errorMessage: import("zod").ZodNullable<import("zod").ZodString>;
|
|
138
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
139
|
+
status: import("zod").ZodLiteral<"expired">;
|
|
140
|
+
}, import("zod/v4/core").$strip>], "status">;
|
|
141
|
+
}, import("zod/v4/core").$strip> | undefined, "UNAUTHENTICATED" | "PROJECT_CONNECTOR_ERROR" | "RETURN_URI_NOT_ALLOWED", string>;
|
|
142
|
+
refreshConnection: FragnoReactMutator<NonGetHTTPMethod, "/connection-requests/:requestId/refresh", import("@standard-schema/spec").StandardSchemaV1<unknown, unknown> | undefined, import("zod").ZodObject<{
|
|
143
|
+
id: import("zod").ZodString;
|
|
144
|
+
projectId: import("zod").ZodString;
|
|
145
|
+
providerConfigId: import("zod").ZodString;
|
|
146
|
+
externalUserId: import("zod").ZodString;
|
|
147
|
+
service: import("zod").ZodString;
|
|
148
|
+
connectionName: import("zod").ZodString;
|
|
149
|
+
authorizationUrl: import("zod").ZodURL;
|
|
150
|
+
expiresAt: import("zod").ZodString;
|
|
151
|
+
state: import("zod").ZodDiscriminatedUnion<[import("zod").ZodObject<{
|
|
152
|
+
status: import("zod").ZodLiteral<"initiated">;
|
|
153
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
154
|
+
status: import("zod").ZodLiteral<"connected">;
|
|
155
|
+
connectedAccountId: import("zod").ZodString;
|
|
156
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
157
|
+
status: import("zod").ZodLiteral<"failed">;
|
|
158
|
+
errorCode: import("zod").ZodNullable<import("zod").ZodString>;
|
|
159
|
+
errorMessage: import("zod").ZodNullable<import("zod").ZodString>;
|
|
160
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
161
|
+
status: import("zod").ZodLiteral<"expired">;
|
|
162
|
+
}, import("zod/v4/core").$strip>], "status">;
|
|
163
|
+
}, import("zod/v4/core").$strip> | undefined, "UNAUTHENTICATED" | "PROJECT_CONNECTOR_ERROR" | "REQUEST_NOT_FOUND", string>;
|
|
164
|
+
executeAction: FragnoReactMutator<NonGetHTTPMethod, "/accounts/:accountId/actions/:actionId", import("zod").ZodObject<{
|
|
165
|
+
input: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>;
|
|
166
|
+
}, import("zod/v4/core").$strict> | undefined, import("zod").ZodObject<{
|
|
167
|
+
executionId: import("zod").ZodString;
|
|
168
|
+
actionId: import("zod").ZodString;
|
|
169
|
+
output: import("zod").ZodUnknown & import("zod").ZodType<{} | null, unknown, import("zod/v4/core").$ZodTypeInternals<{} | null, unknown>>;
|
|
170
|
+
}, import("zod/v4/core").$strip> | undefined, "UNAUTHENTICATED" | "PROJECT_CONNECTOR_ERROR" | "ACCOUNT_NOT_FOUND" | "ACTION_SERVICE_MISMATCH", string>;
|
|
171
|
+
};
|
|
172
|
+
//#endregion
|
|
173
|
+
export { createProjectConnectorFragmentClient };
|
|
174
|
+
//# sourceMappingURL=react.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.d.ts","names":[],"sources":["../../../../fragno/dist/client/react.d.ts","../../../src/client/react.ts"],"mappings":";;;;KASK,gBAAgB,wBAAwB,sBAAsB,sBAAsB,kBAAkB,2BAA2B,oCAAoC;EACxK,OAAO,8BAA8B,gBAAgB;EACrD,QAAQ,gBAAgB,2BAA2B;MAC/C,aAAa,iBAAiB,YAAY,gBAAgB,yBAAyB,YAAY;EACnG;;KAEG,mBAAmB,iBAAiB,kBAAkB,sBAAsB,qBAAqB,8BAA8B,sBAAsB,8BAA8B,2BAA2B;EACjN,WAAW,MAAM,MAAM;IACrB,OAAO,QAAQ;IACf,OAAO,cAAc,sBAAsB,yBAAyB,gBAAgB;IACpF,QAAQ,gBAAgB,2BAA2B;QAC/C,QAAQ,QAAQ;EACtB;EACA,QAAQ,yBAAyB,YAAY;EAC7C,OAAO,QAAQ;;;;;iBCjBD,qCAAqC,SAAQ;sBAAA"}
|