@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.
Files changed (77) hide show
  1. package/LICENSE.md +16 -0
  2. package/README.md +285 -2
  3. package/bin/run.js +4 -0
  4. package/dist/browser/client/react.d.ts +174 -0
  5. package/dist/browser/client/react.d.ts.map +1 -0
  6. package/dist/browser/client/react.js +195 -0
  7. package/dist/browser/client/react.js.map +1 -0
  8. package/dist/browser/client/solid.d.ts +177 -0
  9. package/dist/browser/client/solid.d.ts.map +1 -0
  10. package/dist/browser/client/solid.js +130 -0
  11. package/dist/browser/client/solid.js.map +1 -0
  12. package/dist/browser/client/svelte.d.ts +177 -0
  13. package/dist/browser/client/svelte.d.ts.map +1 -0
  14. package/dist/browser/client/svelte.js +132 -0
  15. package/dist/browser/client/svelte.js.map +1 -0
  16. package/dist/browser/client/vanilla.d.ts +204 -0
  17. package/dist/browser/client/vanilla.d.ts.map +1 -0
  18. package/dist/browser/client/vanilla.js +160 -0
  19. package/dist/browser/client/vanilla.js.map +1 -0
  20. package/dist/browser/client/vue.d.ts +177 -0
  21. package/dist/browser/client/vue.d.ts.map +1 -0
  22. package/dist/browser/client/vue.js +128 -0
  23. package/dist/browser/client/vue.js.map +1 -0
  24. package/dist/browser/client-BCzNDSlW.d.ts +711 -0
  25. package/dist/browser/client-BCzNDSlW.d.ts.map +1 -0
  26. package/dist/browser/index.d.ts +504 -0
  27. package/dist/browser/index.d.ts.map +1 -0
  28. package/dist/browser/index.js +2 -0
  29. package/dist/browser/src-CSPQuoKz.js +2286 -0
  30. package/dist/browser/src-CSPQuoKz.js.map +1 -0
  31. package/dist/cli/authorization-browser.js +20 -0
  32. package/dist/cli/authorization-browser.js.map +1 -0
  33. package/dist/cli/cli.d.ts +6 -0
  34. package/dist/cli/cli.d.ts.map +1 -0
  35. package/dist/cli/cli.js +258 -0
  36. package/dist/cli/cli.js.map +1 -0
  37. package/dist/cli/definition.js +13 -0
  38. package/dist/cli/definition.js.map +1 -0
  39. package/dist/cli/index.js +14 -0
  40. package/dist/cli/index.js.map +1 -0
  41. package/dist/cli/project-connector-client.js +235 -0
  42. package/dist/cli/project-connector-client.js.map +1 -0
  43. package/dist/cli/project-connector-contracts.js +125 -0
  44. package/dist/cli/project-connector-contracts.js.map +1 -0
  45. package/dist/cli/routes.js +449 -0
  46. package/dist/cli/routes.js.map +1 -0
  47. package/dist/cli/schema.js +18 -0
  48. package/dist/cli/schema.js.map +1 -0
  49. package/dist/node/definition.d.ts +170 -0
  50. package/dist/node/definition.d.ts.map +1 -0
  51. package/dist/node/definition.js +13 -0
  52. package/dist/node/definition.js.map +1 -0
  53. package/dist/node/index.d.ts +456 -0
  54. package/dist/node/index.d.ts.map +1 -0
  55. package/dist/node/index.js +36 -0
  56. package/dist/node/index.js.map +1 -0
  57. package/dist/node/project-connector-client.d.ts +13 -0
  58. package/dist/node/project-connector-client.d.ts.map +1 -0
  59. package/dist/node/project-connector-client.js +235 -0
  60. package/dist/node/project-connector-client.js.map +1 -0
  61. package/dist/node/project-connector-contracts.d.ts +179 -0
  62. package/dist/node/project-connector-contracts.d.ts.map +1 -0
  63. package/dist/node/project-connector-contracts.js +125 -0
  64. package/dist/node/project-connector-contracts.js.map +1 -0
  65. package/dist/node/routes.d.ts +307 -0
  66. package/dist/node/routes.d.ts.map +1 -0
  67. package/dist/node/routes.js +449 -0
  68. package/dist/node/routes.js.map +1 -0
  69. package/dist/node/schema.d.ts +49 -0
  70. package/dist/node/schema.d.ts.map +1 -0
  71. package/dist/node/schema.js +18 -0
  72. package/dist/node/schema.js.map +1 -0
  73. package/dist/node/testing/project-connector-test-gateway.d.ts +63 -0
  74. package/dist/node/testing/project-connector-test-gateway.d.ts.map +1 -0
  75. package/dist/node/testing/project-connector-test-gateway.js +370 -0
  76. package/dist/node/testing/project-connector-test-gateway.js.map +1 -0
  77. package/package.json +111 -3
@@ -0,0 +1,125 @@
1
+ import { z } from "zod";
2
+ //#region src/project-connector-contracts.ts
3
+ /** Only HTTP(S) URLs can be used as OAuth return destinations. */
4
+ const projectConnectorHttpUrlSchema = z.url().refine((value) => {
5
+ const url = new URL(value);
6
+ return (url.protocol === "https:" || url.protocol === "http:") && !url.username && !url.password;
7
+ }, "Expected an HTTP(S) URL without embedded credentials");
8
+ /** A connection becomes usable only after the gateway confirms its account ID. */
9
+ const projectConnectorConnectionStateSchema = z.discriminatedUnion("status", [
10
+ z.object({ status: z.literal("initiated") }),
11
+ z.object({
12
+ status: z.literal("connected"),
13
+ connectedAccountId: z.string().min(1)
14
+ }),
15
+ z.object({
16
+ status: z.literal("failed"),
17
+ errorCode: z.string().nullable(),
18
+ errorMessage: z.string().nullable()
19
+ }),
20
+ z.object({ status: z.literal("expired") })
21
+ ]);
22
+ /** Mirrors the gateway's alias rule so invalid names fail here with a usable message. */
23
+ const projectConnectorConnectionNameSchema = z.string().regex(/^[a-z0-9][a-z0-9_-]*$/, "Connection names may only contain lowercase letters, digits, underscores, and hyphens, and must start with a letter or digit").describe("Lowercase letters, digits, underscores, and hyphens; starts with a letter or digit.");
24
+ /** Named selectors preserve exact source identity; user ownership comes only from authentication. */
25
+ const projectConnectorNamedConnectionSchema = z.strictObject({
26
+ projectId: z.string().min(1),
27
+ providerConfigId: z.string().min(1),
28
+ connectionName: projectConnectorConnectionNameSchema
29
+ });
30
+ /** Select exactly one provider and name every connection explicitly. */
31
+ const projectConnectorConnectInputSchema = z.union([z.strictObject({
32
+ service: z.string().regex(/^[a-z0-9_-]+$/),
33
+ connectionName: projectConnectorNamedConnectionSchema.shape.connectionName,
34
+ returnUri: projectConnectorHttpUrlSchema
35
+ }), z.strictObject({
36
+ providerConfigId: projectConnectorNamedConnectionSchema.shape.providerConfigId,
37
+ connectionName: projectConnectorNamedConnectionSchema.shape.connectionName,
38
+ returnUri: projectConnectorHttpUrlSchema
39
+ })]);
40
+ /** Public connection request projection, including its verified lifecycle. */
41
+ const projectConnectorConnectionSchema = z.object({
42
+ id: z.string(),
43
+ projectId: z.string(),
44
+ providerConfigId: z.string(),
45
+ externalUserId: z.string(),
46
+ service: z.string(),
47
+ connectionName: projectConnectorNamedConnectionSchema.shape.connectionName,
48
+ authorizationUrl: projectConnectorHttpUrlSchema,
49
+ expiresAt: z.string(),
50
+ state: projectConnectorConnectionStateSchema
51
+ });
52
+ /** Stored account selectors bind actions to a specific product user and provider. */
53
+ const projectConnectorAccountSchema = z.object({
54
+ id: z.string(),
55
+ projectId: z.string(),
56
+ providerConfigId: z.string(),
57
+ externalUserId: z.string(),
58
+ service: z.string(),
59
+ connectionName: projectConnectorNamedConnectionSchema.shape.connectionName
60
+ });
61
+ /** Missing named requests remain JSON results rather than empty HTTP responses. */
62
+ const projectConnectorNamedRequestSchema = z.strictObject({ request: projectConnectorConnectionSchema.nullable() });
63
+ /** Missing named accounts remain JSON results rather than empty HTTP responses. */
64
+ const projectConnectorNamedAccountSchema = z.strictObject({ account: projectConnectorAccountSchema.nullable() });
65
+ /** Account pagination is owned by the fragment rather than the provider gateway. */
66
+ const projectConnectorAccountsSchema = z.object({
67
+ accounts: z.array(projectConnectorAccountSchema),
68
+ cursor: z.string().nullable(),
69
+ hasNextPage: z.boolean()
70
+ });
71
+ /** Project-scoped OAuth provider overview excludes action IDs and credentials. */
72
+ const projectConnectorProviderConfigsSchema = z.object({
73
+ projectId: z.string().min(1),
74
+ providerConfigs: z.array(z.object({
75
+ id: z.string().min(1),
76
+ service: z.string().min(1),
77
+ displayName: z.string(),
78
+ callbackUrl: projectConnectorHttpUrlSchema,
79
+ effectiveScopes: z.array(z.string().min(1)),
80
+ proxyAvailable: z.boolean()
81
+ }))
82
+ }).refine(({ providerConfigs }) => new Set(providerConfigs.map((config) => config.id)).size === providerConfigs.length, "Provider configuration IDs must be unique");
83
+ /** Authoritative catalog contracts describe action values, not a provider's execution permissions. */
84
+ const projectConnectorActionSchema = z.object({
85
+ id: z.string().min(1),
86
+ service: z.string().min(1),
87
+ name: z.string(),
88
+ description: z.string().nullable(),
89
+ inputSchema: z.record(z.string(), z.unknown()),
90
+ outputSchema: z.record(z.string(), z.unknown())
91
+ });
92
+ /** Only actions allowed by this exact project OAuth configuration are included. */
93
+ const projectConnectorProviderActionsSchema = z.object({
94
+ projectId: projectConnectorProviderConfigsSchema.shape.projectId,
95
+ providerConfigId: projectConnectorProviderConfigsSchema.shape.providerConfigs.element.shape.id,
96
+ actions: z.array(projectConnectorActionSchema)
97
+ });
98
+ /** Project authentication does not imply that an individual provider account is usable. */
99
+ const projectConnectorStatusSchema = z.object({ authenticated: z.literal(true) });
100
+ /** Read-only provider identity associated with one verified account. */
101
+ const projectConnectorProfileSchema = z.object({
102
+ connectedAccountId: z.string(),
103
+ externalUserId: z.string(),
104
+ service: z.string(),
105
+ profile: z.object({
106
+ id: z.string(),
107
+ kind: z.string(),
108
+ username: z.string().nullable(),
109
+ displayName: z.string().nullable(),
110
+ avatarUrl: z.string().nullable(),
111
+ email: z.string().nullable(),
112
+ metadata: z.record(z.string(), z.unknown())
113
+ }),
114
+ fetchedAt: z.number()
115
+ });
116
+ /** Action output stays opaque; execution IDs support upstream troubleshooting. */
117
+ const projectConnectorExecutionSchema = z.object({
118
+ executionId: z.string(),
119
+ actionId: z.string(),
120
+ output: z.unknown().refine((value) => value !== void 0, "Expected action output")
121
+ });
122
+ //#endregion
123
+ export { projectConnectorAccountSchema, projectConnectorAccountsSchema, projectConnectorActionSchema, projectConnectorConnectInputSchema, projectConnectorConnectionSchema, projectConnectorConnectionStateSchema, projectConnectorExecutionSchema, projectConnectorHttpUrlSchema, projectConnectorNamedAccountSchema, projectConnectorNamedConnectionSchema, projectConnectorNamedRequestSchema, projectConnectorProfileSchema, projectConnectorProviderActionsSchema, projectConnectorProviderConfigsSchema, projectConnectorStatusSchema };
124
+
125
+ //# sourceMappingURL=project-connector-contracts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-connector-contracts.js","names":[],"sources":["../../src/project-connector-contracts.ts"],"sourcesContent":["import { z } from \"zod\";\n\n/** Only HTTP(S) URLs can be used as OAuth return destinations. */\nexport const projectConnectorHttpUrlSchema = z.url().refine((value) => {\n const url = new URL(value);\n return (url.protocol === \"https:\" || url.protocol === \"http:\") && !url.username && !url.password;\n}, \"Expected an HTTP(S) URL without embedded credentials\");\n\n/** A connection becomes usable only after the gateway confirms its account ID. */\nexport const projectConnectorConnectionStateSchema = z.discriminatedUnion(\"status\", [\n z.object({ status: z.literal(\"initiated\") }),\n z.object({ status: z.literal(\"connected\"), connectedAccountId: z.string().min(1) }),\n z.object({\n status: z.literal(\"failed\"),\n errorCode: z.string().nullable(),\n errorMessage: z.string().nullable(),\n }),\n z.object({ status: z.literal(\"expired\") }),\n]);\n\n/** Persisted connection lifecycle; provider credentials never enter this fragment. */\nexport type ProjectConnectorConnectionState = z.infer<typeof projectConnectorConnectionStateSchema>;\n\n/** Mirrors the gateway's alias rule so invalid names fail here with a usable message. */\nconst projectConnectorConnectionNameSchema = z\n .string()\n .regex(\n /^[a-z0-9][a-z0-9_-]*$/,\n \"Connection names may only contain lowercase letters, digits, underscores, and hyphens, and must start with a letter or digit\",\n )\n .describe(\"Lowercase letters, digits, underscores, and hyphens; starts with a letter or digit.\");\n\n/** Named selectors preserve exact source identity; user ownership comes only from authentication. */\nexport const projectConnectorNamedConnectionSchema = z.strictObject({\n projectId: z.string().min(1),\n providerConfigId: z.string().min(1),\n connectionName: projectConnectorConnectionNameSchema,\n});\n\n/** Select exactly one provider and name every connection explicitly. */\nexport const projectConnectorConnectInputSchema = z.union([\n z.strictObject({\n service: z.string().regex(/^[a-z0-9_-]+$/),\n connectionName: projectConnectorNamedConnectionSchema.shape.connectionName,\n returnUri: projectConnectorHttpUrlSchema,\n }),\n z.strictObject({\n providerConfigId: projectConnectorNamedConnectionSchema.shape.providerConfigId,\n connectionName: projectConnectorNamedConnectionSchema.shape.connectionName,\n returnUri: projectConnectorHttpUrlSchema,\n }),\n]);\n\n/** Public connection request projection, including its verified lifecycle. */\nexport const projectConnectorConnectionSchema = z.object({\n id: z.string(),\n projectId: z.string(),\n providerConfigId: z.string(),\n externalUserId: z.string(),\n service: z.string(),\n connectionName: projectConnectorNamedConnectionSchema.shape.connectionName,\n authorizationUrl: projectConnectorHttpUrlSchema,\n expiresAt: z.string(),\n state: projectConnectorConnectionStateSchema,\n});\n\n/** Stored account selectors bind actions to a specific product user and provider. */\nexport const projectConnectorAccountSchema = z.object({\n id: z.string(),\n projectId: z.string(),\n providerConfigId: z.string(),\n externalUserId: z.string(),\n service: z.string(),\n connectionName: projectConnectorNamedConnectionSchema.shape.connectionName,\n});\n\n/** Missing named requests remain JSON results rather than empty HTTP responses. */\nexport const projectConnectorNamedRequestSchema = z.strictObject({\n request: projectConnectorConnectionSchema.nullable(),\n});\n\n/** Missing named accounts remain JSON results rather than empty HTTP responses. */\nexport const projectConnectorNamedAccountSchema = z.strictObject({\n account: projectConnectorAccountSchema.nullable(),\n});\n\n/** Account pagination is owned by the fragment rather than the provider gateway. */\nexport const projectConnectorAccountsSchema = z.object({\n accounts: z.array(projectConnectorAccountSchema),\n cursor: z.string().nullable(),\n hasNextPage: z.boolean(),\n});\n\n/** Project-scoped OAuth provider overview excludes action IDs and credentials. */\nexport const projectConnectorProviderConfigsSchema = z\n .object({\n projectId: z.string().min(1),\n providerConfigs: z.array(\n z.object({\n id: z.string().min(1),\n service: z.string().min(1),\n displayName: z.string(),\n callbackUrl: projectConnectorHttpUrlSchema,\n effectiveScopes: z.array(z.string().min(1)),\n proxyAvailable: z.boolean(),\n }),\n ),\n })\n .refine(\n ({ providerConfigs }) =>\n new Set(providerConfigs.map((config) => config.id)).size === providerConfigs.length,\n \"Provider configuration IDs must be unique\",\n );\n\n/** Authoritative catalog contracts describe action values, not a provider's execution permissions. */\nexport const projectConnectorActionSchema = z.object({\n id: z.string().min(1),\n service: z.string().min(1),\n name: z.string(),\n description: z.string().nullable(),\n inputSchema: z.record(z.string(), z.unknown()),\n outputSchema: z.record(z.string(), z.unknown()),\n});\n\n/** Only actions allowed by this exact project OAuth configuration are included. */\nexport const projectConnectorProviderActionsSchema = z.object({\n projectId: projectConnectorProviderConfigsSchema.shape.projectId,\n providerConfigId: projectConnectorProviderConfigsSchema.shape.providerConfigs.element.shape.id,\n actions: z.array(projectConnectorActionSchema),\n});\n\n/** Project authentication does not imply that an individual provider account is usable. */\nexport const projectConnectorStatusSchema = z.object({ authenticated: z.literal(true) });\n\n/** Read-only provider identity associated with one verified account. */\nexport const projectConnectorProfileSchema = z.object({\n connectedAccountId: z.string(),\n externalUserId: z.string(),\n service: z.string(),\n profile: z.object({\n id: z.string(),\n kind: z.string(),\n username: z.string().nullable(),\n displayName: z.string().nullable(),\n avatarUrl: z.string().nullable(),\n email: z.string().nullable(),\n metadata: z.record(z.string(), z.unknown()),\n }),\n fetchedAt: z.number(),\n});\n\n/** Action output stays opaque; execution IDs support upstream troubleshooting. */\nexport const projectConnectorExecutionSchema = z.object({\n executionId: z.string(),\n actionId: z.string(),\n output: z.unknown().refine((value) => value !== undefined, \"Expected action output\"),\n});\n"],"mappings":"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"}
@@ -0,0 +1,449 @@
1
+ import { projectConnectorAccountsSchema, projectConnectorConnectInputSchema, projectConnectorConnectionSchema, projectConnectorExecutionSchema, projectConnectorNamedAccountSchema, projectConnectorNamedConnectionSchema, projectConnectorNamedRequestSchema, projectConnectorProfileSchema, projectConnectorProviderActionsSchema, projectConnectorProviderConfigsSchema, projectConnectorStatusSchema } from "./project-connector-contracts.js";
2
+ import { ProjectConnectorClientError, confirmProjectConnectorRequest } from "./project-connector-client.js";
3
+ import { projectConnectorSchema } from "./schema.js";
4
+ import { projectConnectorFragmentDefinition } from "./definition.js";
5
+ import { z } from "zod";
6
+ import { defineRoutes } from "@fragno-dev/core";
7
+ import { decodeCursor } from "@fragno-dev/db/cursor";
8
+ //#region src/routes.ts
9
+ function serializeProjectConnectorRequest(saved) {
10
+ return {
11
+ id: saved.id.toString(),
12
+ projectId: saved.projectId,
13
+ providerConfigId: saved.providerConfigId,
14
+ externalUserId: saved.externalUserId,
15
+ service: saved.service,
16
+ connectionName: saved.connectionName,
17
+ authorizationUrl: saved.authorizationUrl,
18
+ expiresAt: saved.expiresAt,
19
+ state: saved.state
20
+ };
21
+ }
22
+ /** Authenticated routes never accept a product user ID or account binding from the browser. */
23
+ const projectConnectorRoutes = defineRoutes(projectConnectorFragmentDefinition).create(({ config, deps, defineRoute }) => [
24
+ defineRoute({
25
+ method: "GET",
26
+ path: "/provider-configs",
27
+ outputSchema: projectConnectorProviderConfigsSchema,
28
+ errorCodes: ["UNAUTHENTICATED", "PROJECT_CONNECTOR_ERROR"],
29
+ handler: async function({ headers }, { json, error }) {
30
+ if (!await config.getExternalUserId(headers)) return error({
31
+ code: "UNAUTHENTICATED",
32
+ message: "Authentication required"
33
+ }, 401);
34
+ try {
35
+ return json(await deps.projectConnector.listProviderConfigs());
36
+ } catch (cause) {
37
+ if (!(cause instanceof ProjectConnectorClientError)) throw cause;
38
+ return error({
39
+ code: "PROJECT_CONNECTOR_ERROR",
40
+ message: cause.message
41
+ }, 502);
42
+ }
43
+ }
44
+ }),
45
+ defineRoute({
46
+ method: "GET",
47
+ path: "/provider-configs/:providerConfigId/actions",
48
+ outputSchema: projectConnectorProviderActionsSchema,
49
+ errorCodes: [
50
+ "UNAUTHENTICATED",
51
+ "PROVIDER_CONFIG_NOT_FOUND",
52
+ "PROJECT_CONNECTOR_ERROR"
53
+ ],
54
+ handler: async function({ headers, pathParams }, { json, error }) {
55
+ if (!await config.getExternalUserId(headers)) return error({
56
+ code: "UNAUTHENTICATED",
57
+ message: "Authentication required"
58
+ }, 401);
59
+ try {
60
+ const actions = await deps.projectConnector.listProviderActions(pathParams.providerConfigId);
61
+ if (!actions) return error({
62
+ code: "PROVIDER_CONFIG_NOT_FOUND",
63
+ message: "Provider configuration not found"
64
+ }, 404);
65
+ return json(actions);
66
+ } catch (cause) {
67
+ if (!(cause instanceof ProjectConnectorClientError)) throw cause;
68
+ return error({
69
+ code: "PROJECT_CONNECTOR_ERROR",
70
+ message: cause.message
71
+ }, 502);
72
+ }
73
+ }
74
+ }),
75
+ defineRoute({
76
+ method: "GET",
77
+ path: "/status",
78
+ outputSchema: projectConnectorStatusSchema,
79
+ errorCodes: ["UNAUTHENTICATED", "PROJECT_CONNECTOR_ERROR"],
80
+ handler: async function({ headers }, { json, error }) {
81
+ if (!await config.getExternalUserId(headers)) return error({
82
+ code: "UNAUTHENTICATED",
83
+ message: "Authentication required"
84
+ }, 401);
85
+ try {
86
+ return json(await deps.projectConnector.check());
87
+ } catch (cause) {
88
+ if (!(cause instanceof ProjectConnectorClientError)) throw cause;
89
+ return error({
90
+ code: "PROJECT_CONNECTOR_ERROR",
91
+ message: cause.message
92
+ }, 502);
93
+ }
94
+ }
95
+ }),
96
+ defineRoute({
97
+ method: "POST",
98
+ path: "/connection-requests",
99
+ inputSchema: projectConnectorConnectInputSchema,
100
+ outputSchema: projectConnectorConnectionSchema,
101
+ errorCodes: [
102
+ "UNAUTHENTICATED",
103
+ "RETURN_URI_NOT_ALLOWED",
104
+ "PROJECT_CONNECTOR_ERROR"
105
+ ],
106
+ handler: async function({ headers, input }, { json, error }) {
107
+ const externalUserId = await config.getExternalUserId(headers);
108
+ if (!externalUserId) return error({
109
+ code: "UNAUTHENTICATED",
110
+ message: "Authentication required"
111
+ }, 401);
112
+ const body = await input.valid();
113
+ if (!config.allowedReturnUrls(new URL(body.returnUri))) return error({
114
+ code: "RETURN_URI_NOT_ALLOWED",
115
+ message: "OAuth return URL is not allowed"
116
+ }, 400);
117
+ try {
118
+ const remote = await deps.projectConnector.connect(externalUserId, body);
119
+ if (remote.externalUserId !== externalUserId || remote.connectionName !== body.connectionName || ("providerConfigId" in body ? remote.providerConfigId !== body.providerConfigId : remote.service !== body.service)) throw new ProjectConnectorClientError("connection_identity_mismatch", 502);
120
+ const connection = {
121
+ id: remote.id,
122
+ projectId: remote.projectId,
123
+ providerConfigId: remote.providerConfigId,
124
+ externalUserId,
125
+ service: remote.service,
126
+ connectionName: remote.connectionName,
127
+ authorizationUrl: remote.authorizationUrl,
128
+ expiresAt: remote.expiresAt,
129
+ state: { status: "initiated" }
130
+ };
131
+ const named = {
132
+ projectId: remote.projectId,
133
+ providerConfigId: remote.providerConfigId,
134
+ connectionName: body.connectionName
135
+ };
136
+ await this.handlerTx().retrieve(({ forSchema }) => forSchema(projectConnectorSchema).findFirst("connectedAccount", (b) => b.whereIndex("idx_account_named_connection", (eb) => eb.and(eb("externalUserId", "=", externalUserId), eb("projectId", "=", named.projectId), eb("providerConfigId", "=", named.providerConfigId), eb("connectionName", "=", named.connectionName))))).mutate(({ forSchema, retrieveResult: [account] }) => {
137
+ const uow = forSchema(projectConnectorSchema);
138
+ uow.create("connectionRequest", connection);
139
+ if (!account) uow.triggerHook("onConnectionReadinessChanged", {
140
+ externalUserId,
141
+ service: remote.service,
142
+ connection: named,
143
+ ready: false
144
+ });
145
+ }).execute();
146
+ return json(connection);
147
+ } catch (cause) {
148
+ if (!(cause instanceof ProjectConnectorClientError)) throw cause;
149
+ return error({
150
+ code: "PROJECT_CONNECTOR_ERROR",
151
+ message: cause.message
152
+ }, 502);
153
+ }
154
+ }
155
+ }),
156
+ defineRoute({
157
+ method: "GET",
158
+ path: "/connection-requests/by-name",
159
+ queryParameters: [
160
+ "projectId",
161
+ "providerConfigId",
162
+ "connectionName"
163
+ ],
164
+ outputSchema: projectConnectorNamedRequestSchema,
165
+ errorCodes: [
166
+ "UNAUTHENTICATED",
167
+ "INVALID_SELECTOR",
168
+ "CONNECTION_AMBIGUOUS"
169
+ ],
170
+ handler: async function({ headers, query }, { json, error }) {
171
+ const externalUserId = await config.getExternalUserId(headers);
172
+ if (!externalUserId) return error({
173
+ code: "UNAUTHENTICATED",
174
+ message: "Authentication required"
175
+ }, 401);
176
+ const selector = projectConnectorNamedConnectionSchema.safeParse({
177
+ projectId: query.get("projectId"),
178
+ providerConfigId: query.get("providerConfigId"),
179
+ connectionName: query.get("connectionName")
180
+ });
181
+ if (!selector.success) return error({
182
+ code: "INVALID_SELECTOR",
183
+ message: "Invalid named connection selector"
184
+ }, 400);
185
+ const [page] = await this.handlerTx().retrieve(({ forSchema }) => forSchema(projectConnectorSchema).findWithCursor("connectionRequest", (b) => b.whereIndex("idx_request_named_connection", (eb) => eb.and(eb("externalUserId", "=", externalUserId), eb("projectId", "=", selector.data.projectId), eb("providerConfigId", "=", selector.data.providerConfigId), eb("connectionName", "=", selector.data.connectionName))).orderByIndex("idx_request_named_connection", "asc").pageSize(2))).execute();
186
+ if (page.items.length > 1) return error({
187
+ code: "CONNECTION_AMBIGUOUS",
188
+ message: "Project Connector named connection matches multiple OAuth requests."
189
+ }, 409);
190
+ const [saved] = page.items;
191
+ return json({ request: saved ? serializeProjectConnectorRequest(saved) : null });
192
+ }
193
+ }),
194
+ defineRoute({
195
+ method: "POST",
196
+ path: "/connection-requests/:requestId/refresh",
197
+ outputSchema: projectConnectorConnectionSchema,
198
+ errorCodes: [
199
+ "UNAUTHENTICATED",
200
+ "REQUEST_NOT_FOUND",
201
+ "PROJECT_CONNECTOR_ERROR"
202
+ ],
203
+ handler: async function({ headers, pathParams }, { json, error }) {
204
+ const externalUserId = await config.getExternalUserId(headers);
205
+ if (!externalUserId) return error({
206
+ code: "UNAUTHENTICATED",
207
+ message: "Authentication required"
208
+ }, 401);
209
+ let confirmed = null;
210
+ try {
211
+ const result = await this.handlerTx().retrieve(({ forSchema }) => forSchema(projectConnectorSchema).findFirst("connectionRequest", (b) => b.whereIndex("primary", (eb) => eb("id", "=", pathParams.requestId))).find("connectedAccount", (b) => b.whereIndex("idx_account_external_user_id", (eb) => eb("externalUserId", "=", externalUserId)).select([
212
+ "id",
213
+ "projectId",
214
+ "providerConfigId",
215
+ "service",
216
+ "connectionName"
217
+ ]))).afterRetrieve(async (_uow, [saved]) => {
218
+ confirmed = null;
219
+ if (!saved || saved.externalUserId !== externalUserId || saved.state.status !== "initiated") return;
220
+ const remote = await deps.projectConnector.getConnectionRequest(saved.id.toString());
221
+ confirmed = confirmProjectConnectorRequest({
222
+ ...saved,
223
+ id: saved.id.toString()
224
+ }, remote);
225
+ }).mutate(({ forSchema, retrieveResult: [saved, accounts] }) => {
226
+ if (!saved || saved.externalUserId !== externalUserId) return null;
227
+ const state = confirmed ?? saved.state;
228
+ const uow = forSchema(projectConnectorSchema);
229
+ if (saved.state.status === "initiated" && state.status !== "initiated") {
230
+ uow.update("connectionRequest", saved.id, (b) => b.set({ state }).check());
231
+ if (state.status === "connected") {
232
+ const existing = accounts.find((account) => account.id.toString() === state.connectedAccountId);
233
+ if (existing) uow.delete("connectedAccount", existing.id, (b) => b.check());
234
+ uow.create("connectedAccount", {
235
+ id: state.connectedAccountId,
236
+ projectId: saved.projectId,
237
+ providerConfigId: saved.providerConfigId,
238
+ externalUserId: saved.externalUserId,
239
+ service: saved.service,
240
+ connectionName: saved.connectionName
241
+ });
242
+ const sameName = (named) => (account) => account.projectId === named.projectId && account.providerConfigId === named.providerConfigId && account.connectionName === named.connectionName;
243
+ if (existing && !sameName(saved)(existing) && !accounts.some((account) => account !== existing && sameName(existing)(account))) uow.triggerHook("onConnectionReadinessChanged", {
244
+ externalUserId,
245
+ service: existing.service,
246
+ connection: {
247
+ projectId: existing.projectId,
248
+ providerConfigId: existing.providerConfigId,
249
+ connectionName: existing.connectionName
250
+ },
251
+ ready: false
252
+ });
253
+ if (!accounts.some(sameName(saved))) uow.triggerHook("onConnectionReadinessChanged", {
254
+ externalUserId,
255
+ service: saved.service,
256
+ connection: {
257
+ projectId: saved.projectId,
258
+ providerConfigId: saved.providerConfigId,
259
+ connectionName: saved.connectionName
260
+ },
261
+ ready: true
262
+ });
263
+ }
264
+ }
265
+ return serializeProjectConnectorRequest({
266
+ ...saved,
267
+ state
268
+ });
269
+ }).execute();
270
+ if (!result) return error({
271
+ code: "REQUEST_NOT_FOUND",
272
+ message: "Connection request not found"
273
+ }, 404);
274
+ return json(result);
275
+ } catch (cause) {
276
+ if (!(cause instanceof ProjectConnectorClientError)) throw cause;
277
+ return error({
278
+ code: "PROJECT_CONNECTOR_ERROR",
279
+ message: cause.message
280
+ }, 502);
281
+ }
282
+ }
283
+ }),
284
+ defineRoute({
285
+ method: "GET",
286
+ path: "/accounts",
287
+ queryParameters: ["cursor"],
288
+ outputSchema: projectConnectorAccountsSchema,
289
+ errorCodes: ["UNAUTHENTICATED", "INVALID_CURSOR"],
290
+ handler: async function({ headers, query }, { json, error }) {
291
+ const externalUserId = await config.getExternalUserId(headers);
292
+ if (!externalUserId) return error({
293
+ code: "UNAUTHENTICATED",
294
+ message: "Authentication required"
295
+ }, 401);
296
+ let cursor = null;
297
+ const rawCursor = query.get("cursor");
298
+ if (rawCursor) try {
299
+ cursor = decodeCursor(rawCursor);
300
+ const accountId = cursor.indexValues["id"];
301
+ if (cursor.indexName !== "idx_account_external_user_id" || cursor.orderDirection !== "asc" || cursor.pageSize !== 25 || cursor.indexValues["externalUserId"] !== externalUserId || typeof accountId !== "string" || accountId.length === 0) throw new Error("Account cursor does not match this user or query");
302
+ } catch {
303
+ return error({
304
+ code: "INVALID_CURSOR",
305
+ message: "Invalid account cursor"
306
+ }, 400);
307
+ }
308
+ const [page] = await this.handlerTx().retrieve(({ forSchema }) => forSchema(projectConnectorSchema).findWithCursor("connectedAccount", (b) => {
309
+ const ordered = b.whereIndex("idx_account_external_user_id", (eb) => eb("externalUserId", "=", externalUserId)).orderByIndex("idx_account_external_user_id", "asc").pageSize(25);
310
+ return cursor ? ordered.after(cursor) : ordered;
311
+ })).execute();
312
+ return json({
313
+ accounts: page.items.map((account) => ({
314
+ id: account.id.toString(),
315
+ projectId: account.projectId,
316
+ providerConfigId: account.providerConfigId,
317
+ externalUserId: account.externalUserId,
318
+ service: account.service,
319
+ connectionName: account.connectionName
320
+ })),
321
+ cursor: page.cursor?.encode() ?? null,
322
+ hasNextPage: page.hasNextPage
323
+ });
324
+ }
325
+ }),
326
+ defineRoute({
327
+ method: "GET",
328
+ path: "/accounts/by-name",
329
+ queryParameters: [
330
+ "projectId",
331
+ "providerConfigId",
332
+ "connectionName"
333
+ ],
334
+ outputSchema: projectConnectorNamedAccountSchema,
335
+ errorCodes: [
336
+ "UNAUTHENTICATED",
337
+ "INVALID_SELECTOR",
338
+ "CONNECTION_AMBIGUOUS"
339
+ ],
340
+ handler: async function({ headers, query }, { json, error }) {
341
+ const externalUserId = await config.getExternalUserId(headers);
342
+ if (!externalUserId) return error({
343
+ code: "UNAUTHENTICATED",
344
+ message: "Authentication required"
345
+ }, 401);
346
+ const selector = projectConnectorNamedConnectionSchema.safeParse({
347
+ projectId: query.get("projectId"),
348
+ providerConfigId: query.get("providerConfigId"),
349
+ connectionName: query.get("connectionName")
350
+ });
351
+ if (!selector.success) return error({
352
+ code: "INVALID_SELECTOR",
353
+ message: "Invalid named connection selector"
354
+ }, 400);
355
+ const [page] = await this.handlerTx().retrieve(({ forSchema }) => forSchema(projectConnectorSchema).findWithCursor("connectedAccount", (b) => b.whereIndex("idx_account_named_connection", (eb) => eb.and(eb("externalUserId", "=", externalUserId), eb("projectId", "=", selector.data.projectId), eb("providerConfigId", "=", selector.data.providerConfigId), eb("connectionName", "=", selector.data.connectionName))).orderByIndex("idx_account_named_connection", "asc").pageSize(2))).execute();
356
+ if (page.items.length > 1) return error({
357
+ code: "CONNECTION_AMBIGUOUS",
358
+ message: "Project Connector named connection matches multiple confirmed accounts."
359
+ }, 409);
360
+ const [account] = page.items;
361
+ return json({ account: account ? {
362
+ id: account.id.toString(),
363
+ projectId: account.projectId,
364
+ providerConfigId: account.providerConfigId,
365
+ externalUserId: account.externalUserId,
366
+ service: account.service,
367
+ connectionName: account.connectionName
368
+ } : null });
369
+ }
370
+ }),
371
+ defineRoute({
372
+ method: "GET",
373
+ path: "/accounts/:accountId/profile",
374
+ outputSchema: projectConnectorProfileSchema,
375
+ errorCodes: [
376
+ "UNAUTHENTICATED",
377
+ "ACCOUNT_NOT_FOUND",
378
+ "PROJECT_CONNECTOR_ERROR"
379
+ ],
380
+ handler: async function({ headers, pathParams }, { json, error }) {
381
+ const externalUserId = await config.getExternalUserId(headers);
382
+ if (!externalUserId) return error({
383
+ code: "UNAUTHENTICATED",
384
+ message: "Authentication required"
385
+ }, 401);
386
+ const [account] = await this.handlerTx().retrieve(({ forSchema }) => forSchema(projectConnectorSchema).findFirst("connectedAccount", (b) => b.whereIndex("primary", (eb) => eb("id", "=", pathParams.accountId)))).execute();
387
+ if (!account || account.externalUserId !== externalUserId) return error({
388
+ code: "ACCOUNT_NOT_FOUND",
389
+ message: "Connected account not found"
390
+ }, 404);
391
+ try {
392
+ const profile = await deps.projectConnector.getProfile(account.id.toString());
393
+ if (profile.connectedAccountId !== account.id.toString() || profile.externalUserId !== externalUserId || profile.service !== account.service) throw new ProjectConnectorClientError("account_identity_mismatch", 502);
394
+ return json(profile);
395
+ } catch (cause) {
396
+ if (!(cause instanceof ProjectConnectorClientError)) throw cause;
397
+ return error({
398
+ code: "PROJECT_CONNECTOR_ERROR",
399
+ message: cause.message
400
+ }, 502);
401
+ }
402
+ }
403
+ }),
404
+ defineRoute({
405
+ method: "POST",
406
+ path: "/accounts/:accountId/actions/:actionId",
407
+ inputSchema: z.strictObject({ input: z.record(z.string(), z.unknown()) }),
408
+ outputSchema: projectConnectorExecutionSchema,
409
+ errorCodes: [
410
+ "UNAUTHENTICATED",
411
+ "ACCOUNT_NOT_FOUND",
412
+ "ACTION_SERVICE_MISMATCH",
413
+ "PROJECT_CONNECTOR_ERROR"
414
+ ],
415
+ handler: async function({ headers, pathParams, input }, { json, error }) {
416
+ const externalUserId = await config.getExternalUserId(headers);
417
+ if (!externalUserId) return error({
418
+ code: "UNAUTHENTICATED",
419
+ message: "Authentication required"
420
+ }, 401);
421
+ const body = await input.valid();
422
+ const [account] = await this.handlerTx().retrieve(({ forSchema }) => forSchema(projectConnectorSchema).findFirst("connectedAccount", (b) => b.whereIndex("primary", (eb) => eb("id", "=", pathParams.accountId)))).execute();
423
+ if (!account || account.externalUserId !== externalUserId) return error({
424
+ code: "ACCOUNT_NOT_FOUND",
425
+ message: "Connected account not found"
426
+ }, 404);
427
+ if (!pathParams.actionId.startsWith(`${account.service}.`) || !/^[a-z0-9_-]+\.[a-z0-9_-]+$/.test(pathParams.actionId)) return error({
428
+ code: "ACTION_SERVICE_MISMATCH",
429
+ message: "Action must belong to the connected account service"
430
+ }, 400);
431
+ try {
432
+ return json(await deps.projectConnector.execute({
433
+ ...account,
434
+ id: account.id.toString()
435
+ }, pathParams.actionId, body.input));
436
+ } catch (cause) {
437
+ if (!(cause instanceof ProjectConnectorClientError)) throw cause;
438
+ return error({
439
+ code: "PROJECT_CONNECTOR_ERROR",
440
+ message: cause.message
441
+ }, 502);
442
+ }
443
+ }
444
+ })
445
+ ]);
446
+ //#endregion
447
+ export { projectConnectorRoutes };
448
+
449
+ //# sourceMappingURL=routes.js.map