@b1-road/types 0.1.0-alpha.0 → 0.1.0-alpha.2

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/README.md CHANGED
@@ -27,6 +27,7 @@ Ships dual ESM/CJS builds with type declarations. Zero runtime dependencies.
27
27
  | Entities | The resource shapes returned on the wire (business units, members, roles, permissions, …). |
28
28
  | Inputs | The request-body shapes the API accepts. |
29
29
  | Permissions | The `action:subject` permission algebra and its constants. |
30
+ | Webhooks | Webhook event types, per-event payloads, and the delivery envelope (`RoadWebhookEvent`, `RoadWebhookPayloads`, `ROAD_WEBHOOK_EVENT_TYPES`). |
30
31
  | `@b1-road/types/iam` | IAM control-plane types (scopes, assignments, authorization, sessions). |
31
32
 
32
33
  ```ts
@@ -36,13 +37,17 @@ const baseUrl = `${ROAD_API_URLS.production}/api/${ROAD_API_CONTRACT}`;
36
37
  // → https://api.road.app/api/alpha
37
38
  ```
38
39
 
39
- ## Why a shared package
40
+ ## A contract package — the API is a consumer too
40
41
 
41
42
  Every wire type, permission constant, and hosted URL has exactly one definition.
42
- SDKs import them; they never re-declare. If a binding needs a shape this package
43
- doesn't yet expose, it contributes the shape back here in the same change — so
44
- the React, Nest, and Laravel definitions can never drift. This is the
45
- load-bearing principle of the multi-SDK ecosystem.
43
+ SDKs import them; they never re-declare. **The Road API depends on this package
44
+ as well** not as "an SDK the API consumes," but as the contract both sides
45
+ agree on: the API (the producer) declares the wire shapes here and conforms its
46
+ DTOs to them, while the SDKs (the consumers) read the same shapes. The dependency
47
+ direction `API → @b1-road/types` is correct and intended. If a binding or the API
48
+ needs a shape this package doesn't yet expose, it contributes the shape back here
49
+ in the same change — so the API, React, Nest, and Laravel definitions can never
50
+ drift. This is the load-bearing principle of the ecosystem.
46
51
 
47
52
  ## Versioning
48
53
 
package/dist/iam.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/iam.ts"],"sourcesContent":["/**\n * IAM control-plane wire types. These describe the shapes Road's IAM API\n * speaks: scopes, assignments, authorization checks, effective permissions,\n * token exchange, and sessions.\n *\n * They live behind a subpath (`@b1-road/types/iam`) so the React SDK — which\n * never needs the control-plane — doesn't pay for them in its tree-shaken\n * bundle. The Nest SDK imports from here directly.\n */\n\nimport type { RoadPermission } from \"./permissions\";\n\n/** Scope shape. Three flavors today; platforms may register more over time. */\nexport type ScopeType = \"system\" | \"business_unit\" | \"platform\";\n\n/** Subjects the IAM engine can authorize. `service` covers M2M; `api_key` is legacy. */\nexport type SubjectType = \"user\" | \"service\" | \"api_key\";\n\nexport interface Scope {\n id: string;\n type: ScopeType;\n externalId: string | null;\n parentScopeId: string | null;\n parentScope?: {\n id: string;\n type: ScopeType;\n externalId: string | null;\n } | null;\n metadata: Record<string, unknown>;\n createdAt: string;\n updatedAt?: string;\n}\n\nexport interface CreateScopeInput {\n type: ScopeType;\n externalId?: string | null;\n parentScopeId?: string | null;\n metadata?: Record<string, unknown>;\n}\n\nexport interface Assignment {\n id: string;\n subjectType: SubjectType;\n subjectId: string;\n roleId: string;\n scopeId: string;\n grantedBy: string;\n grantedAt: string;\n expiresAt: string | null;\n}\n\nexport interface CreateAssignmentInput {\n subjectType: SubjectType;\n subjectId: string;\n roleId: string;\n scopeId: string;\n expiresAt?: string | null;\n}\n\nexport interface AuthorizeInput {\n subjectType: SubjectType;\n subjectId: string;\n scopeId: string;\n /** Either a single permission string (`\"read:Member\"`) or the wildcard. */\n permission: RoadPermission | (string & {});\n}\n\nexport interface AuthorizeResult {\n allowed: boolean;\n reason: string;\n /** Scope chain that was walked during evaluation (target → ... → root). */\n evaluatedScopes: string[];\n}\n\nexport interface AuthorizeBatchInput {\n subjectType: SubjectType;\n subjectId: string;\n scopeId: string;\n permissions: Array<RoadPermission | (string & {})>;\n}\n\nexport interface AuthorizeBatchResult {\n results: Array<{ permission: string; allowed: boolean }>;\n}\n\nexport interface EffectivePermissionsResult {\n /** Expanded permissions; `manage:X` has been expanded into the CRUD set. */\n permissions: string[];\n}\n\nexport interface Session {\n id: string;\n deviceName: string;\n ipAddress: string | null;\n userAgent?: string;\n provider: string;\n lastUsedAt: string;\n createdAt: string;\n current: boolean;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
1
+ {"version":3,"sources":["../src/iam.ts"],"sourcesContent":["/**\n * IAM control-plane wire types. These describe the shapes Road's IAM API\n * speaks: scopes, assignments, authorization checks, effective permissions,\n * token exchange, and sessions.\n *\n * They live behind a subpath (`@b1-road/types/iam`) so the React SDK — which\n * never needs the control-plane — doesn't pay for them in its tree-shaken\n * bundle. The Nest SDK imports from here directly.\n */\n\nimport type { RoadPermission } from \"./permissions\";\n\n/** Scope shape. Three flavors today; platforms may register more over time. */\nexport type ScopeType = \"system\" | \"business_unit\" | \"platform\";\n\n/** Subjects the IAM engine can authorize. `service` covers M2M; `api_key` is legacy. */\nexport type SubjectType = \"user\" | \"service\" | \"api_key\";\n\nexport interface Scope {\n id: string;\n type: ScopeType;\n externalId: string | null;\n parentScopeId: string | null;\n parentScope?: {\n id: string;\n type: ScopeType;\n externalId: string | null;\n } | null;\n metadata: Record<string, unknown>;\n createdAt: string;\n updatedAt?: string;\n}\n\nexport interface CreateScopeInput {\n type: ScopeType;\n externalId?: string | null;\n parentScopeId?: string | null;\n metadata?: Record<string, unknown>;\n}\n\nexport interface Assignment {\n id: string;\n subjectType: SubjectType;\n subjectId: string;\n roleId: string;\n scopeId: string;\n grantedBy: string;\n grantedAt: string;\n expiresAt: string | null;\n}\n\nexport interface CreateAssignmentInput {\n subjectType: SubjectType;\n subjectId: string;\n roleId: string;\n scopeId: string;\n expiresAt?: string | null;\n}\n\nexport interface AuthorizeInput {\n subjectType: SubjectType;\n subjectId: string;\n scopeId: string;\n /** Either a single permission string (`\"read:Member\"`) or the wildcard. */\n permission: RoadPermission | (string & {});\n}\n\nexport interface AuthorizeResult {\n allowed: boolean;\n reason: string;\n}\n\nexport interface AuthorizeBatchInput {\n subjectType: SubjectType;\n subjectId: string;\n scopeId: string;\n permissions: Array<RoadPermission | (string & {})>;\n}\n\nexport interface AuthorizeBatchResult {\n results: Array<{ permission: string; allowed: boolean }>;\n}\n\nexport interface EffectivePermissionsResult {\n /** Expanded permissions; `manage:X` has been expanded into the CRUD set. */\n permissions: string[];\n}\n\nexport interface Session {\n id: string;\n deviceName: string;\n ipAddress: string | null;\n userAgent?: string;\n provider: string;\n lastUsedAt: string;\n createdAt: string;\n current: boolean;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
package/dist/iam.d.cts CHANGED
@@ -61,8 +61,6 @@ interface AuthorizeInput {
61
61
  interface AuthorizeResult {
62
62
  allowed: boolean;
63
63
  reason: string;
64
- /** Scope chain that was walked during evaluation (target → ... → root). */
65
- evaluatedScopes: string[];
66
64
  }
67
65
  interface AuthorizeBatchInput {
68
66
  subjectType: SubjectType;
package/dist/iam.d.ts CHANGED
@@ -61,8 +61,6 @@ interface AuthorizeInput {
61
61
  interface AuthorizeResult {
62
62
  allowed: boolean;
63
63
  reason: string;
64
- /** Scope chain that was walked during evaluation (target → ... → root). */
65
- evaluatedScopes: string[];
66
64
  }
67
65
  interface AuthorizeBatchInput {
68
66
  subjectType: SubjectType;
package/dist/index.cjs CHANGED
@@ -24,6 +24,7 @@ __export(src_exports, {
24
24
  ROAD_API_URLS: () => ROAD_API_URLS,
25
25
  ROAD_CORE_ACTIONS: () => ROAD_CORE_ACTIONS,
26
26
  ROAD_CORE_SUBJECTS: () => ROAD_CORE_SUBJECTS,
27
+ ROAD_WEBHOOK_EVENT_TYPES: () => ROAD_WEBHOOK_EVENT_TYPES,
27
28
  ROAD_WILDCARD_PERMISSION: () => ROAD_WILDCARD_PERMISSION
28
29
  });
29
30
  module.exports = __toCommonJS(src_exports);
@@ -55,12 +56,26 @@ var ROAD_CORE_SUBJECTS = [
55
56
  "Invitation"
56
57
  ];
57
58
  var ROAD_WILDCARD_PERMISSION = "*";
59
+
60
+ // src/webhooks.ts
61
+ var ROAD_WEBHOOK_EVENT_TYPES = [
62
+ "organization.invitation.created",
63
+ "organization.invitation.accepted",
64
+ "organization.invitation.rejected",
65
+ "organization.invitation.cancelled",
66
+ "organization.member.joined",
67
+ "organization.member.suspended",
68
+ "organization.member.reinstated",
69
+ "organization.member.removed",
70
+ "organization.member.role-changed"
71
+ ];
58
72
  // Annotate the CommonJS export names for ESM import in node:
59
73
  0 && (module.exports = {
60
74
  ROAD_API_CONTRACT,
61
75
  ROAD_API_URLS,
62
76
  ROAD_CORE_ACTIONS,
63
77
  ROAD_CORE_SUBJECTS,
78
+ ROAD_WEBHOOK_EVENT_TYPES,
64
79
  ROAD_WILDCARD_PERMISSION
65
80
  });
66
81
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/api-contract.ts","../src/api-urls.ts","../src/permissions.ts"],"sourcesContent":["export * from \"./api-contract\";\nexport * from \"./api-urls\";\nexport * from \"./entities\";\nexport * from \"./inputs\";\nexport * from \"./permissions\";\n","/**\n * The Road API **contract version** — the major boundary of the HTTP wire\n * surface. The Road API mounts every route under `/api/<contract>`, and every\n * SDK builds its base path as `${host}/api/${ROAD_API_CONTRACT}`.\n *\n * This is the single source of truth for that value. The Road API imports it\n * for its global prefix; the in-repo clients (admin, tester) and the published\n * SDKs read it from here. It is **not** environment configuration — it is\n * identical in every environment and changes only by a deliberate edit when the\n * wire contract makes a breaking change: `alpha` → `v1` → `v2`.\n *\n * Promoting this from `alpha` to `v1` is the trigger to take the SDKs to their\n * first stable `1.0.0`. See `docs/plans/14-sdk-publishing-and-versioning.md`.\n */\nexport const ROAD_API_CONTRACT = \"alpha\" as const;\n\nexport type RoadApiContract = typeof ROAD_API_CONTRACT;\n","/**\n * Road's hosted API base URLs. SDKs default to ROAD_API_URLS.production; the\n * sandbox URL is the deployed Road sandbox (override via the SDK's apiBaseUrl\n * config if you're running against a different environment, eg. local dev or\n * a private staging cluster).\n *\n * If the deployed sandbox hostname changes, update this constant — it's the\n * single source of truth every SDK reads from.\n */\nexport const ROAD_API_URLS = {\n production: \"https://api.road.app\",\n sandbox: \"https://api.road-sandbox.b1.app\",\n} as const;\n\nexport type RoadEnvironment = keyof typeof ROAD_API_URLS;\n","/**\n * Road IAM permission algebra. Permissions are `${action}:${subject}` strings;\n * the wildcard `\"*\"` short-circuits to true for every action in a scope.\n *\n * The widget catalog calls into actions and subjects Road ships by default\n * (the union types below). Platforms can declare their own actions and\n * subjects in their IAM catalog — those flow through useCan as plain strings,\n * still type-safe via `RoadPermission | (string & {})` in the React SDK.\n */\n\n/**\n * The CRUD verbs Road's IAM engine recognizes, plus `manage` which the\n * engine expands server-side into the full CRUD set for the same subject\n * (`manage:Member` ⇒ also `create|read|update|delete:Member` in the\n * effective-permissions response).\n *\n * Platforms can register their own actions (`approve`, `submit`, `list`, …)\n * — those flow through useCan as plain strings (the `(string & {})` part\n * of PermissionInput in the React SDK), still type-safe via that escape.\n */\nexport type RoadAction = \"create\" | \"read\" | \"update\" | \"delete\" | \"manage\";\n\n/**\n * Built-in subjects Road's own permissions cover. Mirrors the subjects\n * registered in the API's `Subjects` constant\n * (apps/api/src/modules/iam/authorization/constants/subjects.ts), limited\n * to the subjects user-facing widgets actually surface:\n *\n * - BusinessUnit: system-level — create new BUs\n * - BUDashboard: BU-level — read a BU's detail page\n * - BUSettings: BU-level — edit a BU's settings\n * - Member, Role, Permission, Invitation: BU-level CRUD\n *\n * Admin / platform-engineer subjects (System, Platform, AdminUser,\n * PlatformIdentity, …) are not exposed here — they belong to a future\n * developer-facing widget (and admin SDK), not the customer-facing toolkit.\n *\n * Platform-specific subjects (eg. A4L's `Agent`) are not enumerated —\n * platforms register them at runtime under their own scope, and useCan's\n * `(string & {})` accepts them without complaint.\n */\nexport type RoadCoreSubject =\n | \"BusinessUnit\"\n | \"BUDashboard\"\n | \"BUSettings\"\n | \"Member\"\n | \"Role\"\n | \"Permission\"\n | \"Invitation\";\n\n/** `${action}:${Subject}` for the core set, plus the wildcard. */\nexport type RoadPermission = `${RoadAction}:${RoadCoreSubject}` | \"*\";\n\n/** Runtime array of the core actions — handy for iteration or validation. */\nexport const ROAD_CORE_ACTIONS = [\n \"create\",\n \"read\",\n \"update\",\n \"delete\",\n \"manage\",\n] as const satisfies readonly RoadAction[];\n\n/** Runtime array of the core subjects — handy for iteration or validation. */\nexport const ROAD_CORE_SUBJECTS = [\n \"BusinessUnit\",\n \"BUDashboard\",\n \"BUSettings\",\n \"Member\",\n \"Role\",\n \"Permission\",\n \"Invitation\",\n] as const satisfies readonly RoadCoreSubject[];\n\n/** The wildcard permission string. Use this instead of literal \"*\" for grep-ability. */\nexport const ROAD_WILDCARD_PERMISSION = \"*\" as const;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACcO,IAAM,oBAAoB;;;ACL1B,IAAM,gBAAgB;AAAA,EAC3B,YAAY;AAAA,EACZ,SAAS;AACX;;;AC0CO,IAAM,oBAAoB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,qBAAqB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,2BAA2B;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/api-contract.ts","../src/api-urls.ts","../src/permissions.ts","../src/webhooks.ts"],"sourcesContent":["export * from \"./api-contract\";\nexport * from \"./api-urls\";\nexport * from \"./entities\";\nexport * from \"./inputs\";\nexport * from \"./permissions\";\nexport * from \"./webhooks\";\n","/**\n * The Road API **contract version** — the major boundary of the HTTP wire\n * surface. The Road API mounts every route under `/api/<contract>`, and every\n * SDK builds its base path as `${host}/api/${ROAD_API_CONTRACT}`.\n *\n * This is the single source of truth for that value. The Road API imports it\n * for its global prefix; the in-repo clients (admin, tester) and the published\n * SDKs read it from here. It is **not** environment configuration — it is\n * identical in every environment and changes only by a deliberate edit when the\n * wire contract makes a breaking change: `alpha` → `v1` → `v2`.\n *\n * Promoting this from `alpha` to `v1` is the trigger to take the SDKs to their\n * first stable `1.0.0`. See `docs/plans/14-sdk-publishing-and-versioning.md`.\n */\nexport const ROAD_API_CONTRACT = \"alpha\" as const;\n\nexport type RoadApiContract = typeof ROAD_API_CONTRACT;\n","/**\n * Road's hosted API base URLs. SDKs default to ROAD_API_URLS.production; the\n * sandbox URL is the deployed Road sandbox (override via the SDK's apiBaseUrl\n * config if you're running against a different environment, eg. local dev or\n * a private staging cluster).\n *\n * If the deployed sandbox hostname changes, update this constant — it's the\n * single source of truth every SDK reads from.\n */\nexport const ROAD_API_URLS = {\n production: \"https://api.road.app\",\n sandbox: \"https://api.road-sandbox.b1.app\",\n} as const;\n\nexport type RoadEnvironment = keyof typeof ROAD_API_URLS;\n","/**\n * Road IAM permission algebra. Permissions are `${action}:${subject}` strings;\n * the wildcard `\"*\"` short-circuits to true for every action in a scope.\n *\n * The widget catalog calls into actions and subjects Road ships by default\n * (the union types below). Platforms can declare their own actions and\n * subjects in their IAM catalog — those flow through useCan as plain strings,\n * still type-safe via `RoadPermission | (string & {})` in the React SDK.\n */\n\n/**\n * The CRUD verbs Road's IAM engine recognizes, plus `manage` which the\n * engine expands server-side into the full CRUD set for the same subject\n * (`manage:Member` ⇒ also `create|read|update|delete:Member` in the\n * effective-permissions response).\n *\n * Platforms can register their own actions (`approve`, `submit`, `list`, …)\n * — those flow through useCan as plain strings (the `(string & {})` part\n * of PermissionInput in the React SDK), still type-safe via that escape.\n */\nexport type RoadAction = \"create\" | \"read\" | \"update\" | \"delete\" | \"manage\";\n\n/**\n * Built-in subjects Road's own permissions cover. Mirrors the subjects\n * registered in the API's `Subjects` constant\n * (apps/api/src/modules/iam/authorization/constants/subjects.ts), limited\n * to the subjects user-facing widgets actually surface:\n *\n * - BusinessUnit: system-level — create new BUs\n * - BUDashboard: BU-level — read a BU's detail page\n * - BUSettings: BU-level — edit a BU's settings\n * - Member, Role, Permission, Invitation: BU-level CRUD\n *\n * Admin / platform-engineer subjects (System, Platform, AdminUser,\n * PlatformIdentity, …) are not exposed here — they belong to a future\n * developer-facing widget (and admin SDK), not the customer-facing toolkit.\n *\n * Platform-specific subjects (eg. A4L's `Agent`) are not enumerated —\n * platforms register them at runtime under their own scope, and useCan's\n * `(string & {})` accepts them without complaint.\n */\nexport type RoadCoreSubject =\n | \"BusinessUnit\"\n | \"BUDashboard\"\n | \"BUSettings\"\n | \"Member\"\n | \"Role\"\n | \"Permission\"\n | \"Invitation\";\n\n/** `${action}:${Subject}` for the core set, plus the wildcard. */\nexport type RoadPermission = `${RoadAction}:${RoadCoreSubject}` | \"*\";\n\n/** Runtime array of the core actions — handy for iteration or validation. */\nexport const ROAD_CORE_ACTIONS = [\n \"create\",\n \"read\",\n \"update\",\n \"delete\",\n \"manage\",\n] as const satisfies readonly RoadAction[];\n\n/** Runtime array of the core subjects — handy for iteration or validation. */\nexport const ROAD_CORE_SUBJECTS = [\n \"BusinessUnit\",\n \"BUDashboard\",\n \"BUSettings\",\n \"Member\",\n \"Role\",\n \"Permission\",\n \"Invitation\",\n] as const satisfies readonly RoadCoreSubject[];\n\n/** The wildcard permission string. Use this instead of literal \"*\" for grep-ability. */\nexport const ROAD_WILDCARD_PERMISSION = \"*\" as const;\n","/**\n * Road webhook events — the canonical wire contract every SDK builds on.\n *\n * The event-type strings and `data` payloads mirror exactly what the Road API\n * delivers: the `event` and `data` fields of the webhook POST body, and the\n * `X-Road-Event` header. SDKs that receive webhooks import from here and never\n * redefine them (SDK DX Bar principle 11 — one source of truth).\n *\n * Only the public `organization.*` events are listed; internal/experimental\n * event namespaces are intentionally excluded from the SDK surface.\n */\n\n/** Payload for every `organization.invitation.*` event. */\nexport interface InvitationWebhookData {\n businessUnitId: string;\n invitationId: string;\n email: string;\n}\n\n/** Payload for `organization.member.{joined,suspended,reinstated,removed}`. */\nexport interface MemberWebhookData {\n businessUnitId: string;\n memberId: string;\n userId: string;\n}\n\n/** Payload for `organization.member.role-changed`. */\nexport interface MemberRoleChangedWebhookData extends MemberWebhookData {\n roleId: string;\n action: \"assigned\" | \"revoked\";\n}\n\n/** Maps each webhook event type to the shape of its `data` payload. */\nexport interface RoadWebhookPayloads {\n \"organization.invitation.created\": InvitationWebhookData;\n \"organization.invitation.accepted\": InvitationWebhookData;\n \"organization.invitation.rejected\": InvitationWebhookData;\n \"organization.invitation.cancelled\": InvitationWebhookData;\n \"organization.member.joined\": MemberWebhookData;\n \"organization.member.suspended\": MemberWebhookData;\n \"organization.member.reinstated\": MemberWebhookData;\n \"organization.member.removed\": MemberWebhookData;\n \"organization.member.role-changed\": MemberRoleChangedWebhookData;\n}\n\n/** Every webhook event type the Road API can deliver. */\nexport type RoadWebhookEventType = keyof RoadWebhookPayloads;\n\n/**\n * The exact envelope the Road API POSTs to a registered webhook URL —\n * mirrors the delivered body `{ id, event, timestamp, data }`.\n */\nexport interface RoadWebhookEvent<\n T extends RoadWebhookEventType = RoadWebhookEventType,\n> {\n /** Unique delivery id — also sent as the `X-Road-Delivery-Id` header. */\n id: string;\n /** The event type — also sent as the `X-Road-Event` header. */\n event: T;\n /** ISO-8601 timestamp of the delivery. */\n timestamp: string;\n /** Event-specific payload. */\n data: RoadWebhookPayloads[T];\n}\n\n/**\n * Runtime list of every webhook event type — handy for registering a\n * subscription to \"all events\". `satisfies` guarantees no typo'd or\n * stale entry slips in.\n */\nexport const ROAD_WEBHOOK_EVENT_TYPES = [\n \"organization.invitation.created\",\n \"organization.invitation.accepted\",\n \"organization.invitation.rejected\",\n \"organization.invitation.cancelled\",\n \"organization.member.joined\",\n \"organization.member.suspended\",\n \"organization.member.reinstated\",\n \"organization.member.removed\",\n \"organization.member.role-changed\",\n] as const satisfies readonly RoadWebhookEventType[];\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACcO,IAAM,oBAAoB;;;ACL1B,IAAM,gBAAgB;AAAA,EAC3B,YAAY;AAAA,EACZ,SAAS;AACX;;;AC0CO,IAAM,oBAAoB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,qBAAqB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,2BAA2B;;;ACJjC,IAAM,2BAA2B;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;","names":[]}
package/dist/index.d.cts CHANGED
@@ -33,7 +33,7 @@ declare const ROAD_API_URLS: {
33
33
  type RoadEnvironment = keyof typeof ROAD_API_URLS;
34
34
 
35
35
  type BUStatus = "active" | "suspended" | "archived";
36
- type MemberStatus = "active" | "suspended" | "pending";
36
+ type MemberStatus = "active" | "suspended" | "removed";
37
37
  interface CurrentUser {
38
38
  id: string;
39
39
  name: string;
@@ -96,7 +96,14 @@ interface Invitation {
96
96
  email: string;
97
97
  roleId: string;
98
98
  roleName: string;
99
- status: "pending" | "accepted" | "expired" | "cancelled";
99
+ status: "pending" | "accepted" | "expired" | "cancelled" | "rejected";
100
+ /**
101
+ * How the invitation was accepted: `"manual"` (the user explicitly accepted)
102
+ * or `"auto"` (auto-linked when the invited user first signed in with a
103
+ * pending invitation). `null` while still pending — and for invitations
104
+ * accepted before this field existed.
105
+ */
106
+ acceptedVia: "manual" | "auto" | null;
100
107
  invitedAt: string;
101
108
  expiresAt: string;
102
109
  }
@@ -106,7 +113,9 @@ interface Role {
106
113
  description: string;
107
114
  permissions: string[];
108
115
  isSystem: boolean;
109
- memberCount: number;
116
+ /** Count of role assignments (users + service principals + groups). */
117
+ assignmentCount: number;
118
+ createdAt: string;
110
119
  }
111
120
  interface Permission {
112
121
  id: string;
@@ -115,19 +124,42 @@ interface Permission {
115
124
  description: string;
116
125
  /** Subject group (e.g. "Member", "Role", "BusinessUnit") used to group in the picker. */
117
126
  category: string;
127
+ scopeId: string;
128
+ createdAt: string;
118
129
  }
119
130
  /**
120
- * Effective permissions for the current user, keyed by business-unit ID.
121
- * A value of `["*"]` (ROAD_WILDCARD_PERMISSION) grants every action in that BU.
131
+ * Effective permissions for the current user, keyed by business-unit ID, as the
132
+ * **SDK** exposes them each grant flattened to an `"action:subject"` string
133
+ * (`["*"]` grants every action in that BU). This is the client-side convenience
134
+ * shape; the raw wire form is `ScopePermissions` / `ScopePermissionsBulk` below,
135
+ * which the HTTP client maps into this.
122
136
  */
123
137
  type MyPermissions = Record<string, string[]>;
124
138
  /**
125
- * Cursor-paginated list response. The API issues opaque base64-url cursors
126
- * the client treats as opaque no parsing or arithmetic on the SDK side.
139
+ * A single effective-permission grant exactly as the API returns it on the
140
+ * wire: an `action`/`subject` pair. Wildcards are returned as-is
141
+ * (`{ action: "*", subject: "*" }`), not enumerated.
142
+ */
143
+ interface PermissionTuple {
144
+ action: string;
145
+ subject: string;
146
+ }
147
+ /** Wire shape of `GET /me/permissions?scope=<scopeId>` (single scope). */
148
+ interface ScopePermissions {
149
+ permissions: PermissionTuple[];
150
+ }
151
+ /**
152
+ * Wire shape of `GET /me/permissions?scopes=<id>,<id>,…` — effective
153
+ * permissions keyed by scope id (the SDK's fan-in for multi-BU users).
154
+ */
155
+ type ScopePermissionsBulk = Record<string, PermissionTuple[]>;
156
+ /**
157
+ * Cursor-pagination metadata. The API issues opaque base64-url cursors the
158
+ * client treats as opaque — no parsing or arithmetic on the SDK side.
127
159
  * `cursor` is null when no further page exists; `hasMore` is the canonical
128
160
  * "more rows after this page" signal, redundant with `cursor !== null`.
129
161
  */
130
- interface PageInfo {
162
+ interface Pagination {
131
163
  /** Opaque cursor for the next page. Null when this is the last page. */
132
164
  cursor: string | null;
133
165
  /** True iff there is at least one row past this page. */
@@ -136,13 +168,15 @@ interface PageInfo {
136
168
  totalCount: number;
137
169
  }
138
170
  /**
139
- * Generic paged response shape returned from listing endpoints with
140
- * cursor pagination (members, roles, invitations). Convertible 1:1 to
141
- * React Query's useInfiniteQuery `pageParam` flow via `pageInfo.cursor`.
171
+ * Canonical paged response shape for every list endpoint. Mirrors the API's
172
+ * universal `{ data }` envelope the rows live under `data` (the same key
173
+ * as every single-resource response), with cursor `pagination` as a
174
+ * top-level sibling. Convertible 1:1 to React Query's useInfiniteQuery
175
+ * `pageParam` flow via `pagination.cursor`.
142
176
  */
143
177
  interface PaginatedList<T> {
144
- items: T[];
145
- pageInfo: PageInfo;
178
+ data: T[];
179
+ pagination: Pagination;
146
180
  }
147
181
  /**
148
182
  * Input shape for any paginated list call. Both fields are optional —
@@ -150,7 +184,7 @@ interface PaginatedList<T> {
150
184
  * (currently 20).
151
185
  */
152
186
  interface PaginationInput {
153
- /** Opaque cursor from a previous `pageInfo.cursor`. Omit for the first page. */
187
+ /** Opaque cursor from a previous response's `pagination.cursor`. Omit for the first page. */
154
188
  cursor?: string;
155
189
  /** Page size. Default 20, max 100 (server-enforced). */
156
190
  limit?: number;
@@ -163,19 +197,19 @@ interface CreateBusinessUnitInput {
163
197
  }
164
198
  interface UpdateBusinessUnitInput {
165
199
  name?: string;
166
- slug?: string;
200
+ joinCode?: string | null;
167
201
  memberLimit?: number | null;
168
202
  }
169
203
  interface CreateRoleInput {
170
204
  name: string;
171
- description?: string;
205
+ description?: string | null;
172
206
  permissions: string[];
173
207
  /** Source role ID when "clone from existing"; informational only. */
174
208
  cloneFromRoleId?: string;
175
209
  }
176
210
  interface UpdateRoleInput {
177
211
  name?: string;
178
- description?: string;
212
+ description?: string | null;
179
213
  permissions?: string[];
180
214
  }
181
215
  interface CreateInvitationInput {
@@ -183,4 +217,67 @@ interface CreateInvitationInput {
183
217
  roleId: string;
184
218
  }
185
219
 
186
- export { type BUStatus, type BusinessUnitDetail, type BusinessUnitSummary, type CreateBusinessUnitInput, type CreateInvitationInput, type CreateRoleInput, type CurrentUser, type Invitation, type Member, type MemberStatus, type Membership, type MyBusinessUnits, type MyPermissions, type PageInfo, type PaginatedList, type PaginationInput, type PendingInvitation, type Permission, ROAD_API_CONTRACT, ROAD_API_URLS, type RoadApiContract, type RoadEnvironment, type Role, type RoleRef, type UpdateBusinessUnitInput, type UpdateRoleInput };
220
+ /**
221
+ * Road webhook events — the canonical wire contract every SDK builds on.
222
+ *
223
+ * The event-type strings and `data` payloads mirror exactly what the Road API
224
+ * delivers: the `event` and `data` fields of the webhook POST body, and the
225
+ * `X-Road-Event` header. SDKs that receive webhooks import from here and never
226
+ * redefine them (SDK DX Bar principle 11 — one source of truth).
227
+ *
228
+ * Only the public `organization.*` events are listed; internal/experimental
229
+ * event namespaces are intentionally excluded from the SDK surface.
230
+ */
231
+ /** Payload for every `organization.invitation.*` event. */
232
+ interface InvitationWebhookData {
233
+ businessUnitId: string;
234
+ invitationId: string;
235
+ email: string;
236
+ }
237
+ /** Payload for `organization.member.{joined,suspended,reinstated,removed}`. */
238
+ interface MemberWebhookData {
239
+ businessUnitId: string;
240
+ memberId: string;
241
+ userId: string;
242
+ }
243
+ /** Payload for `organization.member.role-changed`. */
244
+ interface MemberRoleChangedWebhookData extends MemberWebhookData {
245
+ roleId: string;
246
+ action: "assigned" | "revoked";
247
+ }
248
+ /** Maps each webhook event type to the shape of its `data` payload. */
249
+ interface RoadWebhookPayloads {
250
+ "organization.invitation.created": InvitationWebhookData;
251
+ "organization.invitation.accepted": InvitationWebhookData;
252
+ "organization.invitation.rejected": InvitationWebhookData;
253
+ "organization.invitation.cancelled": InvitationWebhookData;
254
+ "organization.member.joined": MemberWebhookData;
255
+ "organization.member.suspended": MemberWebhookData;
256
+ "organization.member.reinstated": MemberWebhookData;
257
+ "organization.member.removed": MemberWebhookData;
258
+ "organization.member.role-changed": MemberRoleChangedWebhookData;
259
+ }
260
+ /** Every webhook event type the Road API can deliver. */
261
+ type RoadWebhookEventType = keyof RoadWebhookPayloads;
262
+ /**
263
+ * The exact envelope the Road API POSTs to a registered webhook URL —
264
+ * mirrors the delivered body `{ id, event, timestamp, data }`.
265
+ */
266
+ interface RoadWebhookEvent<T extends RoadWebhookEventType = RoadWebhookEventType> {
267
+ /** Unique delivery id — also sent as the `X-Road-Delivery-Id` header. */
268
+ id: string;
269
+ /** The event type — also sent as the `X-Road-Event` header. */
270
+ event: T;
271
+ /** ISO-8601 timestamp of the delivery. */
272
+ timestamp: string;
273
+ /** Event-specific payload. */
274
+ data: RoadWebhookPayloads[T];
275
+ }
276
+ /**
277
+ * Runtime list of every webhook event type — handy for registering a
278
+ * subscription to "all events". `satisfies` guarantees no typo'd or
279
+ * stale entry slips in.
280
+ */
281
+ declare const ROAD_WEBHOOK_EVENT_TYPES: readonly ["organization.invitation.created", "organization.invitation.accepted", "organization.invitation.rejected", "organization.invitation.cancelled", "organization.member.joined", "organization.member.suspended", "organization.member.reinstated", "organization.member.removed", "organization.member.role-changed"];
282
+
283
+ export { type BUStatus, type BusinessUnitDetail, type BusinessUnitSummary, type CreateBusinessUnitInput, type CreateInvitationInput, type CreateRoleInput, type CurrentUser, type Invitation, type InvitationWebhookData, type Member, type MemberRoleChangedWebhookData, type MemberStatus, type MemberWebhookData, type Membership, type MyBusinessUnits, type MyPermissions, type PaginatedList, type Pagination, type PaginationInput, type PendingInvitation, type Permission, type PermissionTuple, ROAD_API_CONTRACT, ROAD_API_URLS, ROAD_WEBHOOK_EVENT_TYPES, type RoadApiContract, type RoadEnvironment, type RoadWebhookEvent, type RoadWebhookEventType, type RoadWebhookPayloads, type Role, type RoleRef, type ScopePermissions, type ScopePermissionsBulk, type UpdateBusinessUnitInput, type UpdateRoleInput };
package/dist/index.d.ts CHANGED
@@ -33,7 +33,7 @@ declare const ROAD_API_URLS: {
33
33
  type RoadEnvironment = keyof typeof ROAD_API_URLS;
34
34
 
35
35
  type BUStatus = "active" | "suspended" | "archived";
36
- type MemberStatus = "active" | "suspended" | "pending";
36
+ type MemberStatus = "active" | "suspended" | "removed";
37
37
  interface CurrentUser {
38
38
  id: string;
39
39
  name: string;
@@ -96,7 +96,14 @@ interface Invitation {
96
96
  email: string;
97
97
  roleId: string;
98
98
  roleName: string;
99
- status: "pending" | "accepted" | "expired" | "cancelled";
99
+ status: "pending" | "accepted" | "expired" | "cancelled" | "rejected";
100
+ /**
101
+ * How the invitation was accepted: `"manual"` (the user explicitly accepted)
102
+ * or `"auto"` (auto-linked when the invited user first signed in with a
103
+ * pending invitation). `null` while still pending — and for invitations
104
+ * accepted before this field existed.
105
+ */
106
+ acceptedVia: "manual" | "auto" | null;
100
107
  invitedAt: string;
101
108
  expiresAt: string;
102
109
  }
@@ -106,7 +113,9 @@ interface Role {
106
113
  description: string;
107
114
  permissions: string[];
108
115
  isSystem: boolean;
109
- memberCount: number;
116
+ /** Count of role assignments (users + service principals + groups). */
117
+ assignmentCount: number;
118
+ createdAt: string;
110
119
  }
111
120
  interface Permission {
112
121
  id: string;
@@ -115,19 +124,42 @@ interface Permission {
115
124
  description: string;
116
125
  /** Subject group (e.g. "Member", "Role", "BusinessUnit") used to group in the picker. */
117
126
  category: string;
127
+ scopeId: string;
128
+ createdAt: string;
118
129
  }
119
130
  /**
120
- * Effective permissions for the current user, keyed by business-unit ID.
121
- * A value of `["*"]` (ROAD_WILDCARD_PERMISSION) grants every action in that BU.
131
+ * Effective permissions for the current user, keyed by business-unit ID, as the
132
+ * **SDK** exposes them each grant flattened to an `"action:subject"` string
133
+ * (`["*"]` grants every action in that BU). This is the client-side convenience
134
+ * shape; the raw wire form is `ScopePermissions` / `ScopePermissionsBulk` below,
135
+ * which the HTTP client maps into this.
122
136
  */
123
137
  type MyPermissions = Record<string, string[]>;
124
138
  /**
125
- * Cursor-paginated list response. The API issues opaque base64-url cursors
126
- * the client treats as opaque no parsing or arithmetic on the SDK side.
139
+ * A single effective-permission grant exactly as the API returns it on the
140
+ * wire: an `action`/`subject` pair. Wildcards are returned as-is
141
+ * (`{ action: "*", subject: "*" }`), not enumerated.
142
+ */
143
+ interface PermissionTuple {
144
+ action: string;
145
+ subject: string;
146
+ }
147
+ /** Wire shape of `GET /me/permissions?scope=<scopeId>` (single scope). */
148
+ interface ScopePermissions {
149
+ permissions: PermissionTuple[];
150
+ }
151
+ /**
152
+ * Wire shape of `GET /me/permissions?scopes=<id>,<id>,…` — effective
153
+ * permissions keyed by scope id (the SDK's fan-in for multi-BU users).
154
+ */
155
+ type ScopePermissionsBulk = Record<string, PermissionTuple[]>;
156
+ /**
157
+ * Cursor-pagination metadata. The API issues opaque base64-url cursors the
158
+ * client treats as opaque — no parsing or arithmetic on the SDK side.
127
159
  * `cursor` is null when no further page exists; `hasMore` is the canonical
128
160
  * "more rows after this page" signal, redundant with `cursor !== null`.
129
161
  */
130
- interface PageInfo {
162
+ interface Pagination {
131
163
  /** Opaque cursor for the next page. Null when this is the last page. */
132
164
  cursor: string | null;
133
165
  /** True iff there is at least one row past this page. */
@@ -136,13 +168,15 @@ interface PageInfo {
136
168
  totalCount: number;
137
169
  }
138
170
  /**
139
- * Generic paged response shape returned from listing endpoints with
140
- * cursor pagination (members, roles, invitations). Convertible 1:1 to
141
- * React Query's useInfiniteQuery `pageParam` flow via `pageInfo.cursor`.
171
+ * Canonical paged response shape for every list endpoint. Mirrors the API's
172
+ * universal `{ data }` envelope the rows live under `data` (the same key
173
+ * as every single-resource response), with cursor `pagination` as a
174
+ * top-level sibling. Convertible 1:1 to React Query's useInfiniteQuery
175
+ * `pageParam` flow via `pagination.cursor`.
142
176
  */
143
177
  interface PaginatedList<T> {
144
- items: T[];
145
- pageInfo: PageInfo;
178
+ data: T[];
179
+ pagination: Pagination;
146
180
  }
147
181
  /**
148
182
  * Input shape for any paginated list call. Both fields are optional —
@@ -150,7 +184,7 @@ interface PaginatedList<T> {
150
184
  * (currently 20).
151
185
  */
152
186
  interface PaginationInput {
153
- /** Opaque cursor from a previous `pageInfo.cursor`. Omit for the first page. */
187
+ /** Opaque cursor from a previous response's `pagination.cursor`. Omit for the first page. */
154
188
  cursor?: string;
155
189
  /** Page size. Default 20, max 100 (server-enforced). */
156
190
  limit?: number;
@@ -163,19 +197,19 @@ interface CreateBusinessUnitInput {
163
197
  }
164
198
  interface UpdateBusinessUnitInput {
165
199
  name?: string;
166
- slug?: string;
200
+ joinCode?: string | null;
167
201
  memberLimit?: number | null;
168
202
  }
169
203
  interface CreateRoleInput {
170
204
  name: string;
171
- description?: string;
205
+ description?: string | null;
172
206
  permissions: string[];
173
207
  /** Source role ID when "clone from existing"; informational only. */
174
208
  cloneFromRoleId?: string;
175
209
  }
176
210
  interface UpdateRoleInput {
177
211
  name?: string;
178
- description?: string;
212
+ description?: string | null;
179
213
  permissions?: string[];
180
214
  }
181
215
  interface CreateInvitationInput {
@@ -183,4 +217,67 @@ interface CreateInvitationInput {
183
217
  roleId: string;
184
218
  }
185
219
 
186
- export { type BUStatus, type BusinessUnitDetail, type BusinessUnitSummary, type CreateBusinessUnitInput, type CreateInvitationInput, type CreateRoleInput, type CurrentUser, type Invitation, type Member, type MemberStatus, type Membership, type MyBusinessUnits, type MyPermissions, type PageInfo, type PaginatedList, type PaginationInput, type PendingInvitation, type Permission, ROAD_API_CONTRACT, ROAD_API_URLS, type RoadApiContract, type RoadEnvironment, type Role, type RoleRef, type UpdateBusinessUnitInput, type UpdateRoleInput };
220
+ /**
221
+ * Road webhook events — the canonical wire contract every SDK builds on.
222
+ *
223
+ * The event-type strings and `data` payloads mirror exactly what the Road API
224
+ * delivers: the `event` and `data` fields of the webhook POST body, and the
225
+ * `X-Road-Event` header. SDKs that receive webhooks import from here and never
226
+ * redefine them (SDK DX Bar principle 11 — one source of truth).
227
+ *
228
+ * Only the public `organization.*` events are listed; internal/experimental
229
+ * event namespaces are intentionally excluded from the SDK surface.
230
+ */
231
+ /** Payload for every `organization.invitation.*` event. */
232
+ interface InvitationWebhookData {
233
+ businessUnitId: string;
234
+ invitationId: string;
235
+ email: string;
236
+ }
237
+ /** Payload for `organization.member.{joined,suspended,reinstated,removed}`. */
238
+ interface MemberWebhookData {
239
+ businessUnitId: string;
240
+ memberId: string;
241
+ userId: string;
242
+ }
243
+ /** Payload for `organization.member.role-changed`. */
244
+ interface MemberRoleChangedWebhookData extends MemberWebhookData {
245
+ roleId: string;
246
+ action: "assigned" | "revoked";
247
+ }
248
+ /** Maps each webhook event type to the shape of its `data` payload. */
249
+ interface RoadWebhookPayloads {
250
+ "organization.invitation.created": InvitationWebhookData;
251
+ "organization.invitation.accepted": InvitationWebhookData;
252
+ "organization.invitation.rejected": InvitationWebhookData;
253
+ "organization.invitation.cancelled": InvitationWebhookData;
254
+ "organization.member.joined": MemberWebhookData;
255
+ "organization.member.suspended": MemberWebhookData;
256
+ "organization.member.reinstated": MemberWebhookData;
257
+ "organization.member.removed": MemberWebhookData;
258
+ "organization.member.role-changed": MemberRoleChangedWebhookData;
259
+ }
260
+ /** Every webhook event type the Road API can deliver. */
261
+ type RoadWebhookEventType = keyof RoadWebhookPayloads;
262
+ /**
263
+ * The exact envelope the Road API POSTs to a registered webhook URL —
264
+ * mirrors the delivered body `{ id, event, timestamp, data }`.
265
+ */
266
+ interface RoadWebhookEvent<T extends RoadWebhookEventType = RoadWebhookEventType> {
267
+ /** Unique delivery id — also sent as the `X-Road-Delivery-Id` header. */
268
+ id: string;
269
+ /** The event type — also sent as the `X-Road-Event` header. */
270
+ event: T;
271
+ /** ISO-8601 timestamp of the delivery. */
272
+ timestamp: string;
273
+ /** Event-specific payload. */
274
+ data: RoadWebhookPayloads[T];
275
+ }
276
+ /**
277
+ * Runtime list of every webhook event type — handy for registering a
278
+ * subscription to "all events". `satisfies` guarantees no typo'd or
279
+ * stale entry slips in.
280
+ */
281
+ declare const ROAD_WEBHOOK_EVENT_TYPES: readonly ["organization.invitation.created", "organization.invitation.accepted", "organization.invitation.rejected", "organization.invitation.cancelled", "organization.member.joined", "organization.member.suspended", "organization.member.reinstated", "organization.member.removed", "organization.member.role-changed"];
282
+
283
+ export { type BUStatus, type BusinessUnitDetail, type BusinessUnitSummary, type CreateBusinessUnitInput, type CreateInvitationInput, type CreateRoleInput, type CurrentUser, type Invitation, type InvitationWebhookData, type Member, type MemberRoleChangedWebhookData, type MemberStatus, type MemberWebhookData, type Membership, type MyBusinessUnits, type MyPermissions, type PaginatedList, type Pagination, type PaginationInput, type PendingInvitation, type Permission, type PermissionTuple, ROAD_API_CONTRACT, ROAD_API_URLS, ROAD_WEBHOOK_EVENT_TYPES, type RoadApiContract, type RoadEnvironment, type RoadWebhookEvent, type RoadWebhookEventType, type RoadWebhookPayloads, type Role, type RoleRef, type ScopePermissions, type ScopePermissionsBulk, type UpdateBusinessUnitInput, type UpdateRoleInput };
package/dist/index.js CHANGED
@@ -25,11 +25,25 @@ var ROAD_CORE_SUBJECTS = [
25
25
  "Invitation"
26
26
  ];
27
27
  var ROAD_WILDCARD_PERMISSION = "*";
28
+
29
+ // src/webhooks.ts
30
+ var ROAD_WEBHOOK_EVENT_TYPES = [
31
+ "organization.invitation.created",
32
+ "organization.invitation.accepted",
33
+ "organization.invitation.rejected",
34
+ "organization.invitation.cancelled",
35
+ "organization.member.joined",
36
+ "organization.member.suspended",
37
+ "organization.member.reinstated",
38
+ "organization.member.removed",
39
+ "organization.member.role-changed"
40
+ ];
28
41
  export {
29
42
  ROAD_API_CONTRACT,
30
43
  ROAD_API_URLS,
31
44
  ROAD_CORE_ACTIONS,
32
45
  ROAD_CORE_SUBJECTS,
46
+ ROAD_WEBHOOK_EVENT_TYPES,
33
47
  ROAD_WILDCARD_PERMISSION
34
48
  };
35
49
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/api-contract.ts","../src/api-urls.ts","../src/permissions.ts"],"sourcesContent":["/**\n * The Road API **contract version** — the major boundary of the HTTP wire\n * surface. The Road API mounts every route under `/api/<contract>`, and every\n * SDK builds its base path as `${host}/api/${ROAD_API_CONTRACT}`.\n *\n * This is the single source of truth for that value. The Road API imports it\n * for its global prefix; the in-repo clients (admin, tester) and the published\n * SDKs read it from here. It is **not** environment configuration — it is\n * identical in every environment and changes only by a deliberate edit when the\n * wire contract makes a breaking change: `alpha` → `v1` → `v2`.\n *\n * Promoting this from `alpha` to `v1` is the trigger to take the SDKs to their\n * first stable `1.0.0`. See `docs/plans/14-sdk-publishing-and-versioning.md`.\n */\nexport const ROAD_API_CONTRACT = \"alpha\" as const;\n\nexport type RoadApiContract = typeof ROAD_API_CONTRACT;\n","/**\n * Road's hosted API base URLs. SDKs default to ROAD_API_URLS.production; the\n * sandbox URL is the deployed Road sandbox (override via the SDK's apiBaseUrl\n * config if you're running against a different environment, eg. local dev or\n * a private staging cluster).\n *\n * If the deployed sandbox hostname changes, update this constant — it's the\n * single source of truth every SDK reads from.\n */\nexport const ROAD_API_URLS = {\n production: \"https://api.road.app\",\n sandbox: \"https://api.road-sandbox.b1.app\",\n} as const;\n\nexport type RoadEnvironment = keyof typeof ROAD_API_URLS;\n","/**\n * Road IAM permission algebra. Permissions are `${action}:${subject}` strings;\n * the wildcard `\"*\"` short-circuits to true for every action in a scope.\n *\n * The widget catalog calls into actions and subjects Road ships by default\n * (the union types below). Platforms can declare their own actions and\n * subjects in their IAM catalog — those flow through useCan as plain strings,\n * still type-safe via `RoadPermission | (string & {})` in the React SDK.\n */\n\n/**\n * The CRUD verbs Road's IAM engine recognizes, plus `manage` which the\n * engine expands server-side into the full CRUD set for the same subject\n * (`manage:Member` ⇒ also `create|read|update|delete:Member` in the\n * effective-permissions response).\n *\n * Platforms can register their own actions (`approve`, `submit`, `list`, …)\n * — those flow through useCan as plain strings (the `(string & {})` part\n * of PermissionInput in the React SDK), still type-safe via that escape.\n */\nexport type RoadAction = \"create\" | \"read\" | \"update\" | \"delete\" | \"manage\";\n\n/**\n * Built-in subjects Road's own permissions cover. Mirrors the subjects\n * registered in the API's `Subjects` constant\n * (apps/api/src/modules/iam/authorization/constants/subjects.ts), limited\n * to the subjects user-facing widgets actually surface:\n *\n * - BusinessUnit: system-level — create new BUs\n * - BUDashboard: BU-level — read a BU's detail page\n * - BUSettings: BU-level — edit a BU's settings\n * - Member, Role, Permission, Invitation: BU-level CRUD\n *\n * Admin / platform-engineer subjects (System, Platform, AdminUser,\n * PlatformIdentity, …) are not exposed here — they belong to a future\n * developer-facing widget (and admin SDK), not the customer-facing toolkit.\n *\n * Platform-specific subjects (eg. A4L's `Agent`) are not enumerated —\n * platforms register them at runtime under their own scope, and useCan's\n * `(string & {})` accepts them without complaint.\n */\nexport type RoadCoreSubject =\n | \"BusinessUnit\"\n | \"BUDashboard\"\n | \"BUSettings\"\n | \"Member\"\n | \"Role\"\n | \"Permission\"\n | \"Invitation\";\n\n/** `${action}:${Subject}` for the core set, plus the wildcard. */\nexport type RoadPermission = `${RoadAction}:${RoadCoreSubject}` | \"*\";\n\n/** Runtime array of the core actions — handy for iteration or validation. */\nexport const ROAD_CORE_ACTIONS = [\n \"create\",\n \"read\",\n \"update\",\n \"delete\",\n \"manage\",\n] as const satisfies readonly RoadAction[];\n\n/** Runtime array of the core subjects — handy for iteration or validation. */\nexport const ROAD_CORE_SUBJECTS = [\n \"BusinessUnit\",\n \"BUDashboard\",\n \"BUSettings\",\n \"Member\",\n \"Role\",\n \"Permission\",\n \"Invitation\",\n] as const satisfies readonly RoadCoreSubject[];\n\n/** The wildcard permission string. Use this instead of literal \"*\" for grep-ability. */\nexport const ROAD_WILDCARD_PERMISSION = \"*\" as const;\n"],"mappings":";AAcO,IAAM,oBAAoB;;;ACL1B,IAAM,gBAAgB;AAAA,EAC3B,YAAY;AAAA,EACZ,SAAS;AACX;;;AC0CO,IAAM,oBAAoB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,qBAAqB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,2BAA2B;","names":[]}
1
+ {"version":3,"sources":["../src/api-contract.ts","../src/api-urls.ts","../src/permissions.ts","../src/webhooks.ts"],"sourcesContent":["/**\n * The Road API **contract version** — the major boundary of the HTTP wire\n * surface. The Road API mounts every route under `/api/<contract>`, and every\n * SDK builds its base path as `${host}/api/${ROAD_API_CONTRACT}`.\n *\n * This is the single source of truth for that value. The Road API imports it\n * for its global prefix; the in-repo clients (admin, tester) and the published\n * SDKs read it from here. It is **not** environment configuration — it is\n * identical in every environment and changes only by a deliberate edit when the\n * wire contract makes a breaking change: `alpha` → `v1` → `v2`.\n *\n * Promoting this from `alpha` to `v1` is the trigger to take the SDKs to their\n * first stable `1.0.0`. See `docs/plans/14-sdk-publishing-and-versioning.md`.\n */\nexport const ROAD_API_CONTRACT = \"alpha\" as const;\n\nexport type RoadApiContract = typeof ROAD_API_CONTRACT;\n","/**\n * Road's hosted API base URLs. SDKs default to ROAD_API_URLS.production; the\n * sandbox URL is the deployed Road sandbox (override via the SDK's apiBaseUrl\n * config if you're running against a different environment, eg. local dev or\n * a private staging cluster).\n *\n * If the deployed sandbox hostname changes, update this constant — it's the\n * single source of truth every SDK reads from.\n */\nexport const ROAD_API_URLS = {\n production: \"https://api.road.app\",\n sandbox: \"https://api.road-sandbox.b1.app\",\n} as const;\n\nexport type RoadEnvironment = keyof typeof ROAD_API_URLS;\n","/**\n * Road IAM permission algebra. Permissions are `${action}:${subject}` strings;\n * the wildcard `\"*\"` short-circuits to true for every action in a scope.\n *\n * The widget catalog calls into actions and subjects Road ships by default\n * (the union types below). Platforms can declare their own actions and\n * subjects in their IAM catalog — those flow through useCan as plain strings,\n * still type-safe via `RoadPermission | (string & {})` in the React SDK.\n */\n\n/**\n * The CRUD verbs Road's IAM engine recognizes, plus `manage` which the\n * engine expands server-side into the full CRUD set for the same subject\n * (`manage:Member` ⇒ also `create|read|update|delete:Member` in the\n * effective-permissions response).\n *\n * Platforms can register their own actions (`approve`, `submit`, `list`, …)\n * — those flow through useCan as plain strings (the `(string & {})` part\n * of PermissionInput in the React SDK), still type-safe via that escape.\n */\nexport type RoadAction = \"create\" | \"read\" | \"update\" | \"delete\" | \"manage\";\n\n/**\n * Built-in subjects Road's own permissions cover. Mirrors the subjects\n * registered in the API's `Subjects` constant\n * (apps/api/src/modules/iam/authorization/constants/subjects.ts), limited\n * to the subjects user-facing widgets actually surface:\n *\n * - BusinessUnit: system-level — create new BUs\n * - BUDashboard: BU-level — read a BU's detail page\n * - BUSettings: BU-level — edit a BU's settings\n * - Member, Role, Permission, Invitation: BU-level CRUD\n *\n * Admin / platform-engineer subjects (System, Platform, AdminUser,\n * PlatformIdentity, …) are not exposed here — they belong to a future\n * developer-facing widget (and admin SDK), not the customer-facing toolkit.\n *\n * Platform-specific subjects (eg. A4L's `Agent`) are not enumerated —\n * platforms register them at runtime under their own scope, and useCan's\n * `(string & {})` accepts them without complaint.\n */\nexport type RoadCoreSubject =\n | \"BusinessUnit\"\n | \"BUDashboard\"\n | \"BUSettings\"\n | \"Member\"\n | \"Role\"\n | \"Permission\"\n | \"Invitation\";\n\n/** `${action}:${Subject}` for the core set, plus the wildcard. */\nexport type RoadPermission = `${RoadAction}:${RoadCoreSubject}` | \"*\";\n\n/** Runtime array of the core actions — handy for iteration or validation. */\nexport const ROAD_CORE_ACTIONS = [\n \"create\",\n \"read\",\n \"update\",\n \"delete\",\n \"manage\",\n] as const satisfies readonly RoadAction[];\n\n/** Runtime array of the core subjects — handy for iteration or validation. */\nexport const ROAD_CORE_SUBJECTS = [\n \"BusinessUnit\",\n \"BUDashboard\",\n \"BUSettings\",\n \"Member\",\n \"Role\",\n \"Permission\",\n \"Invitation\",\n] as const satisfies readonly RoadCoreSubject[];\n\n/** The wildcard permission string. Use this instead of literal \"*\" for grep-ability. */\nexport const ROAD_WILDCARD_PERMISSION = \"*\" as const;\n","/**\n * Road webhook events — the canonical wire contract every SDK builds on.\n *\n * The event-type strings and `data` payloads mirror exactly what the Road API\n * delivers: the `event` and `data` fields of the webhook POST body, and the\n * `X-Road-Event` header. SDKs that receive webhooks import from here and never\n * redefine them (SDK DX Bar principle 11 — one source of truth).\n *\n * Only the public `organization.*` events are listed; internal/experimental\n * event namespaces are intentionally excluded from the SDK surface.\n */\n\n/** Payload for every `organization.invitation.*` event. */\nexport interface InvitationWebhookData {\n businessUnitId: string;\n invitationId: string;\n email: string;\n}\n\n/** Payload for `organization.member.{joined,suspended,reinstated,removed}`. */\nexport interface MemberWebhookData {\n businessUnitId: string;\n memberId: string;\n userId: string;\n}\n\n/** Payload for `organization.member.role-changed`. */\nexport interface MemberRoleChangedWebhookData extends MemberWebhookData {\n roleId: string;\n action: \"assigned\" | \"revoked\";\n}\n\n/** Maps each webhook event type to the shape of its `data` payload. */\nexport interface RoadWebhookPayloads {\n \"organization.invitation.created\": InvitationWebhookData;\n \"organization.invitation.accepted\": InvitationWebhookData;\n \"organization.invitation.rejected\": InvitationWebhookData;\n \"organization.invitation.cancelled\": InvitationWebhookData;\n \"organization.member.joined\": MemberWebhookData;\n \"organization.member.suspended\": MemberWebhookData;\n \"organization.member.reinstated\": MemberWebhookData;\n \"organization.member.removed\": MemberWebhookData;\n \"organization.member.role-changed\": MemberRoleChangedWebhookData;\n}\n\n/** Every webhook event type the Road API can deliver. */\nexport type RoadWebhookEventType = keyof RoadWebhookPayloads;\n\n/**\n * The exact envelope the Road API POSTs to a registered webhook URL —\n * mirrors the delivered body `{ id, event, timestamp, data }`.\n */\nexport interface RoadWebhookEvent<\n T extends RoadWebhookEventType = RoadWebhookEventType,\n> {\n /** Unique delivery id — also sent as the `X-Road-Delivery-Id` header. */\n id: string;\n /** The event type — also sent as the `X-Road-Event` header. */\n event: T;\n /** ISO-8601 timestamp of the delivery. */\n timestamp: string;\n /** Event-specific payload. */\n data: RoadWebhookPayloads[T];\n}\n\n/**\n * Runtime list of every webhook event type — handy for registering a\n * subscription to \"all events\". `satisfies` guarantees no typo'd or\n * stale entry slips in.\n */\nexport const ROAD_WEBHOOK_EVENT_TYPES = [\n \"organization.invitation.created\",\n \"organization.invitation.accepted\",\n \"organization.invitation.rejected\",\n \"organization.invitation.cancelled\",\n \"organization.member.joined\",\n \"organization.member.suspended\",\n \"organization.member.reinstated\",\n \"organization.member.removed\",\n \"organization.member.role-changed\",\n] as const satisfies readonly RoadWebhookEventType[];\n"],"mappings":";AAcO,IAAM,oBAAoB;;;ACL1B,IAAM,gBAAgB;AAAA,EAC3B,YAAY;AAAA,EACZ,SAAS;AACX;;;AC0CO,IAAM,oBAAoB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,qBAAqB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,2BAA2B;;;ACJjC,IAAM,2BAA2B;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b1-road/types",
3
- "version": "0.1.0-alpha.0",
3
+ "version": "0.1.0-alpha.2",
4
4
  "type": "module",
5
5
  "description": "Shared types and constants for every @b1-road SDK — entities, permission algebra, hosted API URLs.",
6
6
  "license": "MIT",
@@ -27,12 +27,24 @@
27
27
  "types": "./dist/index.d.ts",
28
28
  "exports": {
29
29
  ".": {
30
- "import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" },
31
- "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" }
30
+ "import": {
31
+ "types": "./dist/index.d.ts",
32
+ "default": "./dist/index.js"
33
+ },
34
+ "require": {
35
+ "types": "./dist/index.d.cts",
36
+ "default": "./dist/index.cjs"
37
+ }
32
38
  },
33
39
  "./iam": {
34
- "import": { "types": "./dist/iam.d.ts", "default": "./dist/iam.js" },
35
- "require": { "types": "./dist/iam.d.cts", "default": "./dist/iam.cjs" }
40
+ "import": {
41
+ "types": "./dist/iam.d.ts",
42
+ "default": "./dist/iam.js"
43
+ },
44
+ "require": {
45
+ "types": "./dist/iam.d.cts",
46
+ "default": "./dist/iam.cjs"
47
+ }
36
48
  }
37
49
  },
38
50
  "files": [