@pylonsync/sdk 0.3.385 → 0.4.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/dist/index.d.ts CHANGED
@@ -769,6 +769,12 @@ export declare function policiesToManifest(policies: PolicyDefinition[]): Manife
769
769
  * })
770
770
  */
771
771
  export type AuthConfig = {
772
+ /**
773
+ * Additional organization role slugs. Built-ins (`owner`, `admin`,
774
+ * `member`) remain available. Custom roles are exact-match labels and do
775
+ * not inherit member/admin permissions.
776
+ */
777
+ orgRoles?: string[];
772
778
  user?: {
773
779
  /** Manifest entity name pylon treats as the User table. Default `"User"`. */
774
780
  entity?: string;
@@ -953,6 +959,7 @@ export type ManifestAuthConfig = {
953
959
  invite_entity: string;
954
960
  disabled: boolean;
955
961
  };
962
+ org_roles: string[];
956
963
  trusted_origins: string[];
957
964
  };
958
965
  /**
@@ -971,7 +978,7 @@ export declare function buildManifest(options: {
971
978
  queries?: QueryDefinition[];
972
979
  actions?: ActionDefinition[];
973
980
  policies?: PolicyDefinition[];
974
- auth?: ManifestAuthConfig;
981
+ auth?: ManifestAuthConfig | AuthConfig;
975
982
  llm?: ManifestLlmConfig;
976
983
  connections?: ManifestConnection[];
977
984
  crons?: ManifestCron[];
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.3.385",
6
+ "version": "0.4.1",
7
7
  "type": "module",
8
8
  "main": "./src/index.ts",
9
9
  "types": "./dist/index.d.ts",
package/src/index.ts CHANGED
@@ -1408,6 +1408,12 @@ export function policiesToManifest(
1408
1408
  * })
1409
1409
  */
1410
1410
  export type AuthConfig = {
1411
+ /**
1412
+ * Additional organization role slugs. Built-ins (`owner`, `admin`,
1413
+ * `member`) remain available. Custom roles are exact-match labels and do
1414
+ * not inherit member/admin permissions.
1415
+ */
1416
+ orgRoles?: string[];
1411
1417
  user?: {
1412
1418
  /** Manifest entity name pylon treats as the User table. Default `"User"`. */
1413
1419
  entity?: string;
@@ -1621,9 +1627,34 @@ export type ManifestAuthConfig = {
1621
1627
  invite_entity: string;
1622
1628
  disabled: boolean;
1623
1629
  };
1630
+ org_roles: string[];
1624
1631
  trusted_origins: string[];
1625
1632
  };
1626
1633
 
1634
+ const BUILTIN_ORG_ROLES = new Set(["owner", "admin", "member"]);
1635
+ const ORG_ROLE_PATTERN = /^[a-z][a-z0-9_-]{0,63}$/;
1636
+
1637
+ function validateOrgRoles(roles: string[]): string[] {
1638
+ const seen = new Set<string>();
1639
+ return roles.map((role) => {
1640
+ if (!ORG_ROLE_PATTERN.test(role)) {
1641
+ throw new Error(
1642
+ `Invalid organization role "${role}". Roles must match ${ORG_ROLE_PATTERN}`,
1643
+ );
1644
+ }
1645
+ if (BUILTIN_ORG_ROLES.has(role)) {
1646
+ throw new Error(
1647
+ `Organization role "${role}" is built in and must not be redeclared`,
1648
+ );
1649
+ }
1650
+ if (seen.has(role)) {
1651
+ throw new Error(`Duplicate organization role "${role}"`);
1652
+ }
1653
+ seen.add(role);
1654
+ return role;
1655
+ });
1656
+ }
1657
+
1627
1658
  /**
1628
1659
  * Build the manifest's `auth` block from the user-facing camelCase
1629
1660
  * config. Translates to the snake_case shape the Rust runtime expects.
@@ -1653,10 +1684,17 @@ export function auth(cfg: AuthConfig = {}): ManifestAuthConfig {
1653
1684
  invite_entity: cfg.org?.inviteEntity ?? "OrgInvite",
1654
1685
  disabled: cfg.org?.disabled ?? false,
1655
1686
  },
1687
+ org_roles: validateOrgRoles(cfg.orgRoles ?? []),
1656
1688
  trusted_origins: cfg.trustedOrigins ?? [],
1657
1689
  };
1658
1690
  }
1659
1691
 
1692
+ function isManifestAuthConfig(
1693
+ cfg: ManifestAuthConfig | AuthConfig,
1694
+ ): cfg is ManifestAuthConfig {
1695
+ return "trusted_origins" in cfg;
1696
+ }
1697
+
1660
1698
  export function buildManifest(options: {
1661
1699
  name: string;
1662
1700
  version: string;
@@ -1665,7 +1703,7 @@ export function buildManifest(options: {
1665
1703
  queries?: QueryDefinition[];
1666
1704
  actions?: ActionDefinition[];
1667
1705
  policies?: PolicyDefinition[];
1668
- auth?: ManifestAuthConfig;
1706
+ auth?: ManifestAuthConfig | AuthConfig;
1669
1707
  llm?: ManifestLlmConfig;
1670
1708
  connections?: ManifestConnection[];
1671
1709
  crons?: ManifestCron[];
@@ -1707,7 +1745,11 @@ export function buildManifest(options: {
1707
1745
  queries: queriesToManifest(options.queries ?? []),
1708
1746
  actions: actionsToManifest(options.actions ?? []),
1709
1747
  policies: policiesToManifest(allPolicies),
1710
- auth: options.auth ?? auth(),
1748
+ auth: options.auth
1749
+ ? isManifestAuthConfig(options.auth)
1750
+ ? options.auth
1751
+ : auth(options.auth)
1752
+ : auth(),
1711
1753
  ...(options.llm && Object.keys(options.llm).length > 0
1712
1754
  ? { llm: options.llm }
1713
1755
  : {}),
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import { buildManifest, entity, field } from "./index";
2
+ import { auth, buildManifest, entity, field } from "./index";
3
3
 
4
4
  /**
5
5
  * The shape callers actually hit.
@@ -67,3 +67,38 @@ describe("entity() sync option", () => {
67
67
  .toBe(false);
68
68
  });
69
69
  });
70
+
71
+ describe("custom organization roles", () => {
72
+ const base = {
73
+ name: "roles",
74
+ version: "1",
75
+ entities: [],
76
+ policies: [],
77
+ routes: [],
78
+ };
79
+
80
+ test("buildManifest accepts the concise auth.orgRoles form", () => {
81
+ const manifest = buildManifest({
82
+ ...base,
83
+ auth: { orgRoles: ["reviewer", "speaker_manager"] },
84
+ });
85
+ expect(manifest.auth?.org_roles).toEqual([
86
+ "reviewer",
87
+ "speaker_manager",
88
+ ]);
89
+ });
90
+
91
+ test("the auth() helper emits the same role allowlist", () => {
92
+ expect(auth({ orgRoles: ["billing"] }).org_roles).toEqual(["billing"]);
93
+ });
94
+
95
+ test("invalid, duplicate, and redeclared built-in roles fail clearly", () => {
96
+ expect(() => auth({ orgRoles: ["Reviewer"] })).toThrow(
97
+ "Invalid organization role",
98
+ );
99
+ expect(() => auth({ orgRoles: ["reviewer", "reviewer"] })).toThrow(
100
+ "Duplicate organization role",
101
+ );
102
+ expect(() => auth({ orgRoles: ["member"] })).toThrow("is built in");
103
+ });
104
+ });