Skip to content

fix: add missing extends field to Role types#121

Open
Kyzgor wants to merge 1 commit into
permitio:mainfrom
Kyzgor:fix/add-extends-field-to-role-types
Open

fix: add missing extends field to Role types#121
Kyzgor wants to merge 1 commit into
permitio:mainfrom
Kyzgor:fix/add-extends-field-to-role-types

Conversation

@Kyzgor

@Kyzgor Kyzgor commented Mar 8, 2026

Copy link
Copy Markdown
  • What kind of change does this PR introduce?

Bug fix. Adds the missing extends field to the generated Role and ResourceRole types so SDK consumers can use role inheritance with type safety.

  • What is the current behavior? (You can also link to an open issue here)

The Permit API and the OpenAPI spec define extends (Array<string>) on the role schemas, and the API returns it, but it is missing from the generated TypeScript types. Consumers cannot set or read role inheritance without a type error:

permit.api.roles.create({ key: 'editor', name: 'Editor', extends: ['viewer'] });
// 'extends' does not exist in type 'RoleCreate'
const role = await permit.api.roles.get('editor');
role.extends; // Property 'extends' does not exist on type 'RoleRead'

The same gap affects the resource-scoped roles (permit.api.resourceRoles.*).

This replaces the earlier version of the PR, which bundled unrelated changes. See "Other information".

  • What is the new behavior (if this is a feature change)?

extends?: Array<string> is added to the six generated role interfaces:

  • RoleCreate, RoleRead, RoleUpdate
  • ResourceRoleCreate, ResourceRoleRead, ResourceRoleUpdate

The member matches the generator output after the repo's prettier post-processing: the live-spec doc comment, positioned between attributes and granted_to, so a future regeneration reproduces this exact extends member as a no-op. A full regeneration would also pick up unrelated spec drift on these schemas, such as updated granted_to typing and the v1compat_* fields; that re-baseline is tracked separately in #130. A unit test (src/tests/unit/role-extends.spec.ts) pins the field on all six types for create, update, and read; it fails to compile if any of the additions is reverted.

Check Before After
RoleCreate/ResourceRoleCreate accept extends type error compiles
RoleRead.extends / ResourceRoleRead.extends typed not present Array<string>
RoleUpdate/ResourceRoleUpdate accept extends type error compiles
yarn build pass pass
yarn lint pass (0 errors) pass (0 errors)
yarn test:unit n/a (no test) 49 passed, incl. role-extends
revert the six additions role-extends.spec.ts fails to compile (4 assignability + 6 member-access errors)
  • Other information:

Scope. This PR is now only the extends type addition and its test. The earlier version also rewrote src/logger.ts (pino transport) and bumped several runtime dependencies with a large lockfile change. Both are unrelated to extends, were flagged in review, and are dropped here.

On hand-editing a generated file. The review suggested running yarn generate-openapi-client and committing the regenerated output instead of a hand-edit. That path is currently broken. The pinned generator (6.2.1) cannot read the live spec, which is now OpenAPI 3.1.0, so it regenerates an all-any, type-erased client (247 of 319 type files) and still exits 0. Regenerating today would replace the whole typed client with any, which is worse than a missing field. I reported that separately in #130 with a reproduction and options. Targeted hand-edits to src/openapi/types/*.ts are accepted practice in this repo: be9b59a (Add created_at and updated_at to userRead schema) added two fields to user-read.ts by hand, and c502f4e (add 2 more checks for derived roles settings) added an import and a field to derived-role-rule-create.ts, both single-file edits to files carrying the same "Do not edit the class manually" header. Until #130 is fixed, hand-adding the field is the lowest-risk option, and because it matches generator output it folds into the next clean regeneration as a no-op. The new test guards against silent loss in the meantime.

Not included here, on purpose: a full client regeneration (blocked by #130), the v1compat_* fields, which the spec also places on most of these schemas but which the committed client predates (a separate missing-field gap, intentionally out of scope here), and any change to non-role types.

@zeevmoney zeevmoney left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extends addition itself is correct — it matches the backend exactly (extends: Optional[list[str]] on the shared _Editable base, inherited by RoleCreate/Read/Update; permit-backend .../schemas/schema_role.py:56-60). The blockers are scope and method, not the field.

* @type {Array<string>}
* @memberof RoleRead
*/
extends?: Array<string>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] Hand-editing generated files will be clobbered on regen (applies to role-create/role-update too)

These files carry the 'auto generated by OpenAPI Generator — Do not edit the class manually' header and are produced by yarn generate-openapi-client from the live spec (package.json:54). Since extends is already in the backend/spec, a manual edit diverges from the generator and is overwritten on the next regeneration.

Suggestion: Run yarn generate-openapi-client and commit the regenerated output — it picks up extends (and any other drift) faithfully and survives regen.

Comment thread src/logger.ts Outdated
if (!config.log.json) {
return pino(
options,
pino.transport({ target: 'pino-pretty', options: { levelFirst: true } }),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] Out-of-scope logger rewrite changes default behavior and adds a fragile transport

This is unrelated to the extends fix. The default config is json: false (config.ts:133), and this routes that default path through pino.transport({ target: 'pino-pretty' }). Two problems: (1) it changes the default log format for every consumer (today prettyPrint is a no-op on pino 8, so output is currently JSON); (2) pino.transport spawns a worker thread that resolves pino-pretty at runtime — in bundled (tsup), ESM, or serverless environments this commonly throws at logger creation, which runs during client init (index.ts), so it can break SDK startup, not just logging. No tests.

Suggestion: Drop the logger.ts change from this PR. If the pino-8 prettyPrint deprecation needs fixing, do it in a dedicated, tested PR and verify the transport resolves under the shipped tsup bundle.

Comment thread package.json Outdated
"path-to-regexp": "^6.2.1",
"pino": "8.11.0",
"pino-pretty": "10.2.0",
"axios": "^1.13.5",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] Unrelated dependency bumps + large lockfile churn in a type-only PR

This bumps axios ^1.7.4→^1.13.5, lodash, path-to-regexp, and moves pino/pino-pretty from pinned exact versions to caret ranges, plus 576/363 lines of yarn.lock churn — none related to adding extends, and likely to conflict with the approved #127 (which also touches packaging).

Suggestion: Drop the dep/lockfile changes here; if a security bump (axios) is wanted, do it in its own PR. Keep pino/pino-pretty pinned unless something actually requires a newer version.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to align the SDK’s generated Role TypeScript interfaces with the Permit.io OpenAPI spec by adding the missing extends?: Array<string> field, enabling role inheritance in create/update flows and typed access when reading roles. It also includes runtime dependency upgrades and a logging implementation change to support newer pino/pino-pretty behavior.

Changes:

  • Add extends?: Array<string> to RoleRead, RoleCreate, and RoleUpdate interfaces.
  • Update logger initialization to use pino.transport with pino-pretty when config.log.json is false.
  • Bump multiple runtime dependencies (axios/lodash/path-to-regexp/pino/pino-pretty) and regenerate yarn.lock accordingly.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
yarn.lock Lockfile updates reflecting dependency upgrades and transitive changes.
package.json Updates runtime dependency versions (axios/lodash/path-to-regexp/pino/pino-pretty).
src/logger.ts Reworks logger creation to use pino-pretty transport instead of deprecated prettyPrint.
src/openapi/types/role-create.ts Adds extends?: Array<string> to role creation type.
src/openapi/types/role-read.ts Adds extends?: Array<string> to role read type.
src/openapi/types/role-update.ts Adds extends?: Array<string> to role update type.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/logger.ts Outdated
Comment on lines +13 to +26
const options: pino.LoggerOptions = {
level: config.log.level,
prettyPrint: config.log.json ? { levelFirst: true } : false,
base: { label: config.log.label },
timestamp: pino.stdTimeFunctions.isoTime,
});
};

if (!config.log.json) {
return pino(
options,
pino.transport({ target: 'pino-pretty', options: { levelFirst: true } }),
);
}

return pino(options);
Comment thread package.json
Comment on lines 60 to 66
"@bitauth/libauth": "^1.17.1",
"axios": "^1.7.4",
"lodash": "^4.17.21",
"path-to-regexp": "^6.2.1",
"pino": "8.11.0",
"pino-pretty": "10.2.0",
"axios": "^1.13.5",
"lodash": "^4.17.23",
"path-to-regexp": "^6.3.0",
"pino": "^8.21.0",
"pino-pretty": "^10.3.1",
"require-in-the-middle": "^5.1.0",
Comment on lines +55 to +60
/**
* list of role keys that define what roles this role extends. In other words: this role will automatically inherit all the permissions of the given roles in this list.
* @type {Array<string>}
* @memberof RoleCreate
*/
extends?: Array<string>;
Comment on lines +49 to +54
/**
* list of role keys that define what roles this role extends. In other words: this role will automatically inherit all the permissions of the given roles in this list.
* @type {Array<string>}
* @memberof RoleRead
*/
extends?: Array<string>;
Comment on lines +49 to +54
/**
* list of role keys that define what roles this role extends. In other words: this role will automatically inherit all the permissions of the given roles in this list.
* @type {Array<string>}
* @memberof RoleUpdate
*/
extends?: Array<string>;
Comment thread src/logger.ts Outdated
Comment on lines +19 to +24
if (!config.log.json) {
return pino(
options,
pino.transport({ target: 'pino-pretty', options: { levelFirst: true } }),
);
}
The Permit API and OpenAPI spec define extends (Array<string>) on the role
schemas, but it is missing from the generated TypeScript types, so consumers
cannot type-safely set or read role inheritance.

Add extends?: Array<string> to RoleCreate/Read/Update and
ResourceRoleCreate/Read/Update, matching the generator's emitted style and
field position so a future regeneration is a no-op. Add a unit test pinning
the field on all six types; it fails to compile if any addition is reverted.

The canonical fix (regenerate the client) is currently blocked: the pinned
generator 6.2.1 cannot read the now-OpenAPI-3.1 spec and regenerates an
all-any client (see permitio#130).
@Kyzgor Kyzgor force-pushed the fix/add-extends-field-to-role-types branch from c57d16c to 023f1ce Compare June 25, 2026 15:30
@Kyzgor

Kyzgor commented Jun 25, 2026

Copy link
Copy Markdown
Author

Thanks for the review. Agreed on scope and the missing test, both fixed below. One note on the regenerate suggestion.

Scope: dropped the logger.ts rewrite and the dependency/lockfile changes. The PR is now only the extends type addition plus a test.

Test: added src/tests/unit/role-extends.spec.ts, which pins extends on all six types (create/read/update for Role and ResourceRole). It fails to compile if any of the additions is reverted.

On regenerating instead of hand-editing: I tried that first, and yarn generate-openapi-client is broken right now. The pinned generator (6.2.1) can't read the live spec, which is now OpenAPI 3.1.0, so it regenerates an all-any client (247 of 319 type files lose their types) and exits 0. Running it today would replace the whole typed client with any, not just add extends. I wrote it up with a reproduction and a few options in #130. Until that's sorted, the hand-add matches what a working generator emits (same doc comment, same position between attributes and granted_to), so it folds into the next clean regen as a no-op. This follows existing practice in the repo, e.g. be9b59a (created_at/updated_at on userRead) and c502f4e (a field on derived-role-rule-create), both manual edits to src/openapi/types/*.ts.

I also extended it to the ResourceRole types (create/read/update), which carry extends in the spec and had the same gap.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants