@pylonsync/functions 0.22.27 → 0.22.29

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/dist/types.d.ts CHANGED
@@ -746,29 +746,35 @@ export interface LlmCompleteResponse {
746
746
  };
747
747
  }
748
748
  /**
749
- * Server-side OAuth connection registry. Apps declare connections
750
- * via `defineConnection({...})` in `app.ts`; this surface lets
751
- * actions fetch fresh access tokens (auto-refresh) and start the
752
- * OAuth dance.
749
+ * Server-side connection registry. Apps declare connections via
750
+ * `defineConnection({...})` in `app.ts`; this surface fetches fresh
751
+ * access tokens (auto-refresh), starts OAuth flows, and stores API keys.
753
752
  *
754
753
  * Available on mutation + action ctx only — connections perform
755
754
  * external I/O (token refresh, DB writes) that doesn't belong
756
755
  * inside a reactive query.
757
756
  *
758
- * All ops require an authenticated caller (`ctx.auth.userId !==
759
- * null`). Public functions must `ctx.auth.elevate({ admin: true,
760
- * reason: "..." })` before reaching `ctx.connections.*`.
757
+ * Who may do what:
758
+ * - `owner: "user"` (default): the signed-in caller's own connection.
759
+ * Every op needs a signed-in, non-guest caller.
760
+ * - `owner: "app"`: one connection for the whole app. `get` works for
761
+ * any caller, including an anonymous visitor's request, so the
762
+ * function decides what the caller may do with it. `authorizeUrl`,
763
+ * `setCredentials`, and `disconnect` need an admin
764
+ * (`ctx.auth.elevate({ admin: true, reason })`).
761
765
  */
762
766
  export interface Connections {
763
767
  /**
764
- * Mint the URL the browser should navigate to so the user can
765
- * link an external account. `name` matches a `defineConnection({...})`
766
- * entry. `postRedirect` (optional) is where the browser lands
767
- * after a successful callback — defaults to `/`.
768
+ * Mint the URL the browser should navigate to so the account can be
769
+ * linked. `name` matches an OAuth `defineConnection({...})` entry.
770
+ * `postRedirect` (optional, a relative path) is where the browser
771
+ * lands after a successful callback — defaults to `/`.
768
772
  *
769
773
  * Throws `CONNECTIONS_NOT_CONFIGURED`, `CONNECTION_UNKNOWN`,
770
- * `PROVIDER_NOT_CONFIGURED`, or `ENCRYPTION_REQUIRED` (refresh
771
- * tokens are not allowed to land in plaintext).
774
+ * `CONNECTION_FORBIDDEN`, `CONNECTION_WRONG_KIND`,
775
+ * `CONNECTION_MANAGED` (the hosting platform runs this app
776
+ * connection's flow), `PROVIDER_NOT_CONFIGURED`, or
777
+ * `ENCRYPTION_REQUIRED` (tokens are not allowed to land in plaintext).
772
778
  */
773
779
  authorizeUrl(name: string, opts?: {
774
780
  postRedirect?: string;
@@ -776,31 +782,54 @@ export interface Connections {
776
782
  url: string;
777
783
  }>;
778
784
  /**
779
- * Returns a fresh access token for `(ctx.auth.userId, name)`. If
780
- * the stored token expires within 60s, the framework refreshes
781
- * via the provider's refresh-token grant FIRST, persists the new
782
- * token pair, then returns the new access token.
785
+ * Returns a fresh access token. If the stored token expires within
786
+ * 60s, the framework refreshes it first (through the hosting
787
+ * platform for a brokered app connection), persists the new pair,
788
+ * then returns it. For an API-key connection `accessToken` is the
789
+ * first key field and `credentials` holds every field.
783
790
  *
784
- * Throws `CONNECTION_NOT_LINKED` when the user hasn't started
785
- * the OAuth flow, `REFRESH_FAILED` when the provider rejects
786
- * the refresh token (user must re-link).
791
+ * `accountId` is the provider's id for the connected account
792
+ * (Square merchant, Stripe account, Clover merchant). `metadata`
793
+ * holds non-secret provider data (Mailchimp's `dc`).
794
+ *
795
+ * Throws `CONNECTION_NOT_LINKED` when nothing is connected yet,
796
+ * `REFRESH_FAILED` when the provider rejects the refresh token (the
797
+ * account must be linked again).
787
798
  */
788
799
  get(name: string): Promise<{
789
800
  accessToken: string;
790
801
  scope: string | null;
791
802
  expiresAt: number | null;
803
+ accountId: string | null;
804
+ metadata: Record<string, unknown> | null;
805
+ credentials: Record<string, string> | null;
792
806
  }>;
793
- /** List the signed-in user's linked connections. Token values
794
- * are NOT included — call `get(name)` for those. */
807
+ /** The linked connections the caller can see: their own and the
808
+ * app's. Token values are NOT included — call `get(name)`. */
795
809
  list(): Promise<{
796
810
  connections: Array<{
797
811
  name: string;
798
812
  provider: string;
813
+ owner: "user" | "app";
814
+ kind: "oauth" | "api_key";
815
+ accountId: string | null;
799
816
  scope: string | null;
800
817
  expiresAt: number | null;
801
818
  updatedAt: number;
802
819
  }>;
803
820
  }>;
821
+ /**
822
+ * Store an API-key connection. `credentials` must have exactly the
823
+ * connection's fields. For a known provider (`stripe`, `square`,
824
+ * `toast`) the provider is called first to confirm the key works.
825
+ *
826
+ * Throws `CONNECTION_WRONG_KIND`, `INVALID_CREDENTIALS`, or
827
+ * `CREDENTIAL_CHECK_FAILED`.
828
+ */
829
+ setCredentials(name: string, credentials: Record<string, string>): Promise<{
830
+ connected: boolean;
831
+ accountId: string | null;
832
+ }>;
804
833
  /** Remove the stored connection. Provider-side revocation is
805
834
  * the caller's responsibility — most providers expose a separate
806
835
  * `/revoke` endpoint that this surface intentionally doesn't
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pylonsync/functions",
3
- "version": "0.22.27",
3
+ "version": "0.22.29",
4
4
  "description": "TypeScript function runtime for pylon — defines server-side queries, mutations, and actions.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/runtime.ts CHANGED
@@ -1174,11 +1174,21 @@ function buildConnections(callId: string): Connections {
1174
1174
  type: "connection",
1175
1175
  op: "get",
1176
1176
  payload: { name },
1177
- })) as { access_token: string; scope: string | null; expires_at: number | null };
1177
+ })) as {
1178
+ access_token: string;
1179
+ scope: string | null;
1180
+ expires_at: number | null;
1181
+ account_id: string | null;
1182
+ metadata: Record<string, unknown> | null;
1183
+ credentials: Record<string, string> | null;
1184
+ };
1178
1185
  return {
1179
1186
  accessToken: data.access_token,
1180
1187
  scope: data.scope,
1181
1188
  expiresAt: data.expires_at,
1189
+ accountId: data.account_id ?? null,
1190
+ metadata: data.metadata ?? null,
1191
+ credentials: data.credentials ?? null,
1182
1192
  };
1183
1193
  },
1184
1194
  async list() {
@@ -1190,21 +1200,39 @@ function buildConnections(callId: string): Connections {
1190
1200
  connections: Array<{
1191
1201
  name: string;
1192
1202
  provider: string;
1203
+ owner: "user" | "app";
1204
+ kind: "oauth" | "api_key";
1205
+ connected: boolean;
1206
+ account_id: string | null;
1193
1207
  scope: string | null;
1194
1208
  expires_at: number | null;
1195
- updated_at: number;
1209
+ updated_at: number | null;
1196
1210
  }>;
1197
1211
  };
1212
+ // Linked connections only, as before app connections existed.
1198
1213
  return {
1199
- connections: data.connections.map((c) => ({
1200
- name: c.name,
1201
- provider: c.provider,
1202
- scope: c.scope,
1203
- expiresAt: c.expires_at,
1204
- updatedAt: c.updated_at,
1205
- })),
1214
+ connections: data.connections
1215
+ .filter((c) => c.connected)
1216
+ .map((c) => ({
1217
+ name: c.name,
1218
+ provider: c.provider,
1219
+ owner: c.owner,
1220
+ kind: c.kind,
1221
+ accountId: c.account_id,
1222
+ scope: c.scope,
1223
+ expiresAt: c.expires_at,
1224
+ updatedAt: c.updated_at ?? 0,
1225
+ })),
1206
1226
  };
1207
1227
  },
1228
+ async setCredentials(name, credentials) {
1229
+ const data = (await rpc(callId, {
1230
+ type: "connection",
1231
+ op: "set_credentials",
1232
+ payload: { name, credentials },
1233
+ })) as { connected: boolean; account_id: string | null };
1234
+ return { connected: data.connected, accountId: data.account_id ?? null };
1235
+ },
1208
1236
  async disconnect(name) {
1209
1237
  return (await rpc(callId, {
1210
1238
  type: "connection",
package/src/types.ts CHANGED
@@ -840,29 +840,35 @@ export interface LlmCompleteResponse {
840
840
  // ---------------------------------------------------------------------------
841
841
 
842
842
  /**
843
- * Server-side OAuth connection registry. Apps declare connections
844
- * via `defineConnection({...})` in `app.ts`; this surface lets
845
- * actions fetch fresh access tokens (auto-refresh) and start the
846
- * OAuth dance.
843
+ * Server-side connection registry. Apps declare connections via
844
+ * `defineConnection({...})` in `app.ts`; this surface fetches fresh
845
+ * access tokens (auto-refresh), starts OAuth flows, and stores API keys.
847
846
  *
848
847
  * Available on mutation + action ctx only — connections perform
849
848
  * external I/O (token refresh, DB writes) that doesn't belong
850
849
  * inside a reactive query.
851
850
  *
852
- * All ops require an authenticated caller (`ctx.auth.userId !==
853
- * null`). Public functions must `ctx.auth.elevate({ admin: true,
854
- * reason: "..." })` before reaching `ctx.connections.*`.
851
+ * Who may do what:
852
+ * - `owner: "user"` (default): the signed-in caller's own connection.
853
+ * Every op needs a signed-in, non-guest caller.
854
+ * - `owner: "app"`: one connection for the whole app. `get` works for
855
+ * any caller, including an anonymous visitor's request, so the
856
+ * function decides what the caller may do with it. `authorizeUrl`,
857
+ * `setCredentials`, and `disconnect` need an admin
858
+ * (`ctx.auth.elevate({ admin: true, reason })`).
855
859
  */
856
860
  export interface Connections {
857
861
  /**
858
- * Mint the URL the browser should navigate to so the user can
859
- * link an external account. `name` matches a `defineConnection({...})`
860
- * entry. `postRedirect` (optional) is where the browser lands
861
- * after a successful callback — defaults to `/`.
862
+ * Mint the URL the browser should navigate to so the account can be
863
+ * linked. `name` matches an OAuth `defineConnection({...})` entry.
864
+ * `postRedirect` (optional, a relative path) is where the browser
865
+ * lands after a successful callback — defaults to `/`.
862
866
  *
863
867
  * Throws `CONNECTIONS_NOT_CONFIGURED`, `CONNECTION_UNKNOWN`,
864
- * `PROVIDER_NOT_CONFIGURED`, or `ENCRYPTION_REQUIRED` (refresh
865
- * tokens are not allowed to land in plaintext).
868
+ * `CONNECTION_FORBIDDEN`, `CONNECTION_WRONG_KIND`,
869
+ * `CONNECTION_MANAGED` (the hosting platform runs this app
870
+ * connection's flow), `PROVIDER_NOT_CONFIGURED`, or
871
+ * `ENCRYPTION_REQUIRED` (tokens are not allowed to land in plaintext).
866
872
  */
867
873
  authorizeUrl(
868
874
  name: string,
@@ -870,33 +876,57 @@ export interface Connections {
870
876
  ): Promise<{ url: string }>;
871
877
 
872
878
  /**
873
- * Returns a fresh access token for `(ctx.auth.userId, name)`. If
874
- * the stored token expires within 60s, the framework refreshes
875
- * via the provider's refresh-token grant FIRST, persists the new
876
- * token pair, then returns the new access token.
879
+ * Returns a fresh access token. If the stored token expires within
880
+ * 60s, the framework refreshes it first (through the hosting
881
+ * platform for a brokered app connection), persists the new pair,
882
+ * then returns it. For an API-key connection `accessToken` is the
883
+ * first key field and `credentials` holds every field.
877
884
  *
878
- * Throws `CONNECTION_NOT_LINKED` when the user hasn't started
879
- * the OAuth flow, `REFRESH_FAILED` when the provider rejects
880
- * the refresh token (user must re-link).
885
+ * `accountId` is the provider's id for the connected account
886
+ * (Square merchant, Stripe account, Clover merchant). `metadata`
887
+ * holds non-secret provider data (Mailchimp's `dc`).
888
+ *
889
+ * Throws `CONNECTION_NOT_LINKED` when nothing is connected yet,
890
+ * `REFRESH_FAILED` when the provider rejects the refresh token (the
891
+ * account must be linked again).
881
892
  */
882
893
  get(name: string): Promise<{
883
894
  accessToken: string;
884
895
  scope: string | null;
885
896
  expiresAt: number | null;
897
+ accountId: string | null;
898
+ metadata: Record<string, unknown> | null;
899
+ credentials: Record<string, string> | null;
886
900
  }>;
887
901
 
888
- /** List the signed-in user's linked connections. Token values
889
- * are NOT included — call `get(name)` for those. */
902
+ /** The linked connections the caller can see: their own and the
903
+ * app's. Token values are NOT included — call `get(name)`. */
890
904
  list(): Promise<{
891
905
  connections: Array<{
892
906
  name: string;
893
907
  provider: string;
908
+ owner: "user" | "app";
909
+ kind: "oauth" | "api_key";
910
+ accountId: string | null;
894
911
  scope: string | null;
895
912
  expiresAt: number | null;
896
913
  updatedAt: number;
897
914
  }>;
898
915
  }>;
899
916
 
917
+ /**
918
+ * Store an API-key connection. `credentials` must have exactly the
919
+ * connection's fields. For a known provider (`stripe`, `square`,
920
+ * `toast`) the provider is called first to confirm the key works.
921
+ *
922
+ * Throws `CONNECTION_WRONG_KIND`, `INVALID_CREDENTIALS`, or
923
+ * `CREDENTIAL_CHECK_FAILED`.
924
+ */
925
+ setCredentials(
926
+ name: string,
927
+ credentials: Record<string, string>
928
+ ): Promise<{ connected: boolean; accountId: string | null }>;
929
+
900
930
  /** Remove the stored connection. Provider-side revocation is
901
931
  * the caller's responsibility — most providers expose a separate
902
932
  * `/revoke` endpoint that this surface intentionally doesn't