@pylonsync/functions 0.5.4 → 0.5.6
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/index.d.ts +1 -1
- package/dist/types.d.ts +63 -0
- package/package.json +1 -1
- package/src/index.ts +5 -0
- package/src/runtime.ts +67 -0
- package/src/types.ts +61 -0
package/dist/index.d.ts
CHANGED
|
@@ -26,4 +26,4 @@ export type { WorkflowDefinition, WorkflowRun, WorkflowRunRequest, WorkflowRunne
|
|
|
26
26
|
export { resetDb, installTestIsolation } from "./testing";
|
|
27
27
|
export { slugifyName, availableSlug } from "./slugify";
|
|
28
28
|
export type { SsrResponse, SsrCookieOptions, SsrMetadata, Sitemap, SitemapEntry, Robots, RobotsRule, } from "./ssr-runtime";
|
|
29
|
-
export type { QueryCtx, MutationCtx, ActionCtx, DbReader, DbWriter, Stream, Scheduler, AuthInfo, AuthMode, AuthRequirement, FnDefinition, Validator, AnyValidator, ValidatorSchema, InferValidator, InferArgs, RequireMember, RequireMemberOptions, MemberRow, Workflows, VectorSearchQuery, VectorSearchResult, SearchResult, PaginationResult, Llm, LlmMessage, LlmContentBlock, LlmTool, LlmCompleteRequest, LlmCompleteResponse, LlmStreamEvent, Rooms, } from "./types";
|
|
29
|
+
export type { QueryCtx, MutationCtx, ActionCtx, DbReader, DbWriter, Stream, Scheduler, AuthInfo, AuthMode, AuthRequirement, FnDefinition, Validator, AnyValidator, ValidatorSchema, InferValidator, InferArgs, RequireMember, RequireMemberOptions, MemberRow, Workflows, VectorSearchQuery, VectorSearchResult, SearchResult, PaginationResult, Llm, LlmMessage, LlmContentBlock, LlmTool, LlmCompleteRequest, LlmCompleteResponse, LlmStreamEvent, Rooms, Domains, TenantDomainResult, TenantDomainDns, } from "./types";
|
package/dist/types.d.ts
CHANGED
|
@@ -775,6 +775,66 @@ export interface MutationCtx<R extends AuthRequirement = "optional"> {
|
|
|
775
775
|
signal?: AbortSignal;
|
|
776
776
|
}
|
|
777
777
|
/** Context for action handlers (external I/O, non-transactional). */
|
|
778
|
+
/** One DNS record a tenant must set (name + value), as returned by the
|
|
779
|
+
* registrar/CDN. Used for the CNAME target's DCV + ownership TXT records. */
|
|
780
|
+
export interface TenantDomainDns {
|
|
781
|
+
name: string;
|
|
782
|
+
value: string;
|
|
783
|
+
}
|
|
784
|
+
/** The result of attaching / polling a tenant (platform) custom domain —
|
|
785
|
+
* everything your app shows its end-customer to complete DNS setup. */
|
|
786
|
+
export interface TenantDomainResult {
|
|
787
|
+
hostname: string;
|
|
788
|
+
/** "provisioning" until the cert is active, then "ready". */
|
|
789
|
+
status: string;
|
|
790
|
+
/** Whether the hostname + TLS certificate are both active. */
|
|
791
|
+
active?: boolean;
|
|
792
|
+
/** The single CNAME the customer points their hostname at. */
|
|
793
|
+
cnameTarget: string;
|
|
794
|
+
/** TXT record proving domain ownership before DNS cutover (or null). */
|
|
795
|
+
ownership: TenantDomainDns | null;
|
|
796
|
+
/** TXT records that issue the DV certificate. */
|
|
797
|
+
dcv: TenantDomainDns[];
|
|
798
|
+
/** Human-readable provisioning errors, empty when healthy. */
|
|
799
|
+
errors: string[];
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Platform domains: attach / register / detach custom domains for your app's
|
|
803
|
+
* OWN end-customers, so each tenant can reach your app on their own hostname.
|
|
804
|
+
*
|
|
805
|
+
* Cloud-only — proxies to the Pylon Cloud control plane (which holds the
|
|
806
|
+
* Cloudflare/registrar credentials the app never sees). On a self-hosted Pylon
|
|
807
|
+
* every method throws a typed `DOMAINS_NOT_CONFIGURED` error.
|
|
808
|
+
*
|
|
809
|
+
* A newly attached hostname is trusted by the app's CORS / WebSocket / SSR
|
|
810
|
+
* gates automatically (the runtime refreshes the trusted-host set from the
|
|
811
|
+
* control plane) — no redeploy per tenant.
|
|
812
|
+
*
|
|
813
|
+
* @example
|
|
814
|
+
* ```ts
|
|
815
|
+
* // In an action, when your customer adds their domain:
|
|
816
|
+
* const d = await ctx.domains.add("app.customer.com");
|
|
817
|
+
* // Show them: CNAME app.customer.com -> d.cnameTarget, plus d.dcv TXT records.
|
|
818
|
+
* // Later, poll:
|
|
819
|
+
* const s = await ctx.domains.status("app.customer.com"); // s.active === true
|
|
820
|
+
* ```
|
|
821
|
+
*/
|
|
822
|
+
export interface Domains {
|
|
823
|
+
/** Attach a custom domain for one of your end-customers. Returns the DNS the
|
|
824
|
+
* customer must set (the CNAME target + ownership/DCV TXT records). */
|
|
825
|
+
add(hostname: string): Promise<TenantDomainResult>;
|
|
826
|
+
/** Poll a tenant domain's verification + certificate status. */
|
|
827
|
+
status(hostname: string): Promise<TenantDomainResult>;
|
|
828
|
+
/** Detach a tenant domain (deletes the hostname + frees the slot). */
|
|
829
|
+
remove(hostname: string): Promise<{
|
|
830
|
+
hostname: string;
|
|
831
|
+
removed: boolean;
|
|
832
|
+
}>;
|
|
833
|
+
/** The hostnames currently ready (trusted) for this app — owner + tenant. */
|
|
834
|
+
list(): Promise<{
|
|
835
|
+
hosts: string[];
|
|
836
|
+
}>;
|
|
837
|
+
}
|
|
778
838
|
export interface ActionCtx<R extends AuthRequirement = "optional"> {
|
|
779
839
|
auth: AuthInfo<R>;
|
|
780
840
|
stream: Stream;
|
|
@@ -789,6 +849,9 @@ export interface ActionCtx<R extends AuthRequirement = "optional"> {
|
|
|
789
849
|
connections: Connections;
|
|
790
850
|
/** Durable workflows: start / deliver events — see {@link Workflows}. */
|
|
791
851
|
workflows: Workflows;
|
|
852
|
+
/** Attach / register custom domains for your app's OWN end-customers
|
|
853
|
+
* (platform domains) — see {@link Domains}. Cloud-only. */
|
|
854
|
+
domains: Domains;
|
|
792
855
|
/** Environment variables / secrets. */
|
|
793
856
|
env: Record<string, string>;
|
|
794
857
|
/** Signed file-download URLs — see {@link Files}. */
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -90,4 +90,9 @@ export type {
|
|
|
90
90
|
LlmCompleteResponse,
|
|
91
91
|
LlmStreamEvent,
|
|
92
92
|
Rooms,
|
|
93
|
+
// Platform domains — app authors name these when handling ctx.domains
|
|
94
|
+
// results (the DNS the end-customer must set).
|
|
95
|
+
Domains,
|
|
96
|
+
TenantDomainResult,
|
|
97
|
+
TenantDomainDns,
|
|
93
98
|
} from "./types";
|
package/src/runtime.ts
CHANGED
|
@@ -31,6 +31,8 @@ import type {
|
|
|
31
31
|
Rooms,
|
|
32
32
|
Workflows,
|
|
33
33
|
Connections,
|
|
34
|
+
Domains,
|
|
35
|
+
TenantDomainResult,
|
|
34
36
|
QueryCtx,
|
|
35
37
|
MutationCtx,
|
|
36
38
|
ActionCtx,
|
|
@@ -1056,6 +1058,70 @@ function buildConnections(callId: string): Connections {
|
|
|
1056
1058
|
};
|
|
1057
1059
|
}
|
|
1058
1060
|
|
|
1061
|
+
/**
|
|
1062
|
+
* `ctx.domains` — platform (tenant) custom domains. Pure TS: proxies to the
|
|
1063
|
+
* Pylon Cloud control plane over HTTP, authed by the machine's own
|
|
1064
|
+
* `PYLON_DOMAINS_TOKEN` (a plain env var stamped at deploy — not a shared
|
|
1065
|
+
* credential, so no Rust-side transport hook like ctx.email needs). Fails LOUD
|
|
1066
|
+
* with `DOMAINS_NOT_CONFIGURED` when the cloud env is absent (self-host), rather
|
|
1067
|
+
* than silently no-opping — a call that looks like it attached a domain but
|
|
1068
|
+
* didn't would be a worse footgun.
|
|
1069
|
+
*/
|
|
1070
|
+
function buildDomains(): Domains {
|
|
1071
|
+
const cloudUrl = (process.env.PYLON_CLOUD_URL || "").replace(/\/+$/, "");
|
|
1072
|
+
const token = process.env.PYLON_DOMAINS_TOKEN || "";
|
|
1073
|
+
|
|
1074
|
+
async function call<T>(
|
|
1075
|
+
endpoint: string,
|
|
1076
|
+
body: Record<string, unknown>,
|
|
1077
|
+
): Promise<T> {
|
|
1078
|
+
if (!cloudUrl || !token) {
|
|
1079
|
+
const e = new Error(
|
|
1080
|
+
"custom domains require Pylon Cloud (PYLON_CLOUD_URL / PYLON_DOMAINS_TOKEN are not set on this machine)",
|
|
1081
|
+
);
|
|
1082
|
+
(e as any).code = "DOMAINS_NOT_CONFIGURED";
|
|
1083
|
+
throw e;
|
|
1084
|
+
}
|
|
1085
|
+
const res = await fetch(`${cloudUrl}/api/fn/${endpoint}`, {
|
|
1086
|
+
method: "POST",
|
|
1087
|
+
headers: {
|
|
1088
|
+
"Content-Type": "application/json",
|
|
1089
|
+
Authorization: `Bearer ${token}`,
|
|
1090
|
+
},
|
|
1091
|
+
body: JSON.stringify(body),
|
|
1092
|
+
});
|
|
1093
|
+
const text = await res.text();
|
|
1094
|
+
let json: any = {};
|
|
1095
|
+
try {
|
|
1096
|
+
json = text ? JSON.parse(text) : {};
|
|
1097
|
+
} catch {
|
|
1098
|
+
json = {};
|
|
1099
|
+
}
|
|
1100
|
+
// /api/fn returns the handler's raw value on 200, or
|
|
1101
|
+
// {error:{code,message}} on failure (crates/router json_error).
|
|
1102
|
+
if (!res.ok || json?.error) {
|
|
1103
|
+
const err = new Error(
|
|
1104
|
+
json?.error?.message || `platform-domains ${endpoint} failed (${res.status})`,
|
|
1105
|
+
);
|
|
1106
|
+
(err as any).code = json?.error?.code || "DOMAINS_REQUEST_FAILED";
|
|
1107
|
+
throw err;
|
|
1108
|
+
}
|
|
1109
|
+
return json as T;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
return {
|
|
1113
|
+
add: (hostname: string) =>
|
|
1114
|
+
call<TenantDomainResult>("provisionTenantDomain", { hostname }),
|
|
1115
|
+
status: (hostname: string) =>
|
|
1116
|
+
call<TenantDomainResult>("getTenantDomainStatus", { hostname }),
|
|
1117
|
+
remove: (hostname: string) =>
|
|
1118
|
+
call<{ hostname: string; removed: boolean }>("removeTenantDomain", {
|
|
1119
|
+
hostname,
|
|
1120
|
+
}),
|
|
1121
|
+
list: () => call<{ hosts: string[] }>("listProjectTrustedHosts", {}),
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1059
1125
|
function buildActionCtx(
|
|
1060
1126
|
callId: string,
|
|
1061
1127
|
auth: AuthInfo,
|
|
@@ -1089,6 +1155,7 @@ function buildActionCtx(
|
|
|
1089
1155
|
rooms,
|
|
1090
1156
|
connections,
|
|
1091
1157
|
workflows: buildWorkflows(callId),
|
|
1158
|
+
domains: buildDomains(),
|
|
1092
1159
|
env: process.env as Record<string, string>,
|
|
1093
1160
|
async runQuery(fnName, args) {
|
|
1094
1161
|
return rpc(callId, {
|
package/src/types.ts
CHANGED
|
@@ -873,6 +873,64 @@ export interface MutationCtx<R extends AuthRequirement = "optional"> {
|
|
|
873
873
|
}
|
|
874
874
|
|
|
875
875
|
/** Context for action handlers (external I/O, non-transactional). */
|
|
876
|
+
/** One DNS record a tenant must set (name + value), as returned by the
|
|
877
|
+
* registrar/CDN. Used for the CNAME target's DCV + ownership TXT records. */
|
|
878
|
+
export interface TenantDomainDns {
|
|
879
|
+
name: string;
|
|
880
|
+
value: string;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/** The result of attaching / polling a tenant (platform) custom domain —
|
|
884
|
+
* everything your app shows its end-customer to complete DNS setup. */
|
|
885
|
+
export interface TenantDomainResult {
|
|
886
|
+
hostname: string;
|
|
887
|
+
/** "provisioning" until the cert is active, then "ready". */
|
|
888
|
+
status: string;
|
|
889
|
+
/** Whether the hostname + TLS certificate are both active. */
|
|
890
|
+
active?: boolean;
|
|
891
|
+
/** The single CNAME the customer points their hostname at. */
|
|
892
|
+
cnameTarget: string;
|
|
893
|
+
/** TXT record proving domain ownership before DNS cutover (or null). */
|
|
894
|
+
ownership: TenantDomainDns | null;
|
|
895
|
+
/** TXT records that issue the DV certificate. */
|
|
896
|
+
dcv: TenantDomainDns[];
|
|
897
|
+
/** Human-readable provisioning errors, empty when healthy. */
|
|
898
|
+
errors: string[];
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* Platform domains: attach / register / detach custom domains for your app's
|
|
903
|
+
* OWN end-customers, so each tenant can reach your app on their own hostname.
|
|
904
|
+
*
|
|
905
|
+
* Cloud-only — proxies to the Pylon Cloud control plane (which holds the
|
|
906
|
+
* Cloudflare/registrar credentials the app never sees). On a self-hosted Pylon
|
|
907
|
+
* every method throws a typed `DOMAINS_NOT_CONFIGURED` error.
|
|
908
|
+
*
|
|
909
|
+
* A newly attached hostname is trusted by the app's CORS / WebSocket / SSR
|
|
910
|
+
* gates automatically (the runtime refreshes the trusted-host set from the
|
|
911
|
+
* control plane) — no redeploy per tenant.
|
|
912
|
+
*
|
|
913
|
+
* @example
|
|
914
|
+
* ```ts
|
|
915
|
+
* // In an action, when your customer adds their domain:
|
|
916
|
+
* const d = await ctx.domains.add("app.customer.com");
|
|
917
|
+
* // Show them: CNAME app.customer.com -> d.cnameTarget, plus d.dcv TXT records.
|
|
918
|
+
* // Later, poll:
|
|
919
|
+
* const s = await ctx.domains.status("app.customer.com"); // s.active === true
|
|
920
|
+
* ```
|
|
921
|
+
*/
|
|
922
|
+
export interface Domains {
|
|
923
|
+
/** Attach a custom domain for one of your end-customers. Returns the DNS the
|
|
924
|
+
* customer must set (the CNAME target + ownership/DCV TXT records). */
|
|
925
|
+
add(hostname: string): Promise<TenantDomainResult>;
|
|
926
|
+
/** Poll a tenant domain's verification + certificate status. */
|
|
927
|
+
status(hostname: string): Promise<TenantDomainResult>;
|
|
928
|
+
/** Detach a tenant domain (deletes the hostname + frees the slot). */
|
|
929
|
+
remove(hostname: string): Promise<{ hostname: string; removed: boolean }>;
|
|
930
|
+
/** The hostnames currently ready (trusted) for this app — owner + tenant. */
|
|
931
|
+
list(): Promise<{ hosts: string[] }>;
|
|
932
|
+
}
|
|
933
|
+
|
|
876
934
|
export interface ActionCtx<R extends AuthRequirement = "optional"> {
|
|
877
935
|
auth: AuthInfo<R>;
|
|
878
936
|
stream: Stream;
|
|
@@ -887,6 +945,9 @@ export interface ActionCtx<R extends AuthRequirement = "optional"> {
|
|
|
887
945
|
connections: Connections;
|
|
888
946
|
/** Durable workflows: start / deliver events — see {@link Workflows}. */
|
|
889
947
|
workflows: Workflows;
|
|
948
|
+
/** Attach / register custom domains for your app's OWN end-customers
|
|
949
|
+
* (platform domains) — see {@link Domains}. Cloud-only. */
|
|
950
|
+
domains: Domains;
|
|
890
951
|
/** Environment variables / secrets. */
|
|
891
952
|
env: Record<string, string>;
|
|
892
953
|
/** Signed file-download URLs — see {@link Files}. */
|