Skip to content

feat!: Generate organizations APIs using oagen#1628

Draft
gjtorikian wants to merge 1 commit into
mainfrom
oagen/own-organizations
Draft

feat!: Generate organizations APIs using oagen#1628
gjtorikian wants to merge 1 commit into
mainfrom
oagen/own-organizations

Conversation

@gjtorikian

Copy link
Copy Markdown
Contributor

Description

This PR generates the Organizations category in the Node SDK from the OpenAPI spec.

Several of the methods had their signatures changed:

  ┌─────────────────────────────┬──────────────────────────────────────────────────────┬─────────────────────────────────────────────────┐
  │           Method            │                        Before                        │                      After                      │
  ├─────────────────────────────┼──────────────────────────────────────────────────────┼─────────────────────────────────────────────────┤
  │ getOrganization             │ getOrganization(id: string)                          │ getOrganization({ id })                         │
  ├─────────────────────────────┼──────────────────────────────────────────────────────┼─────────────────────────────────────────────────┤
  │ getOrganizationByExternalId │ getOrganizationByExternalId(externalId: string)      │ getOrganizationByExternalId({ externalId })     │
  ├─────────────────────────────┼──────────────────────────────────────────────────────┼─────────────────────────────────────────────────┤
  │ deleteOrganization          │ deleteOrganization(id: string)                       │ deleteOrganization({ id })                      │
  ├─────────────────────────────┼──────────────────────────────────────────────────────┼─────────────────────────────────────────────────┤
  │ updateOrganization          │ updateOrganization({ organization: 'org_...', ... }) │ updateOrganization({ id: 'org_...', ... })      │
  ├─────────────────────────────┼──────────────────────────────────────────────────────┼─────────────────────────────────────────────────┤
  │ createOrganization          │ createOrganization(payload, requestOptions?)         │ createOrganization(options) — 2nd param dropped │
  └─────────────────────────────┴──────────────────────────────────────────────────────┴─────────────────────────────────────────────────┘

As well, the following output responses changed:

  • createdAt / updatedAt are now Date objects, not ISO strings.
    as JSON, or comparing it as a string will break at runtime.

    • allowProfilesOutsideOrganization went from required boolean to optional (boolean | undefined).
    • externalId and metadata are now optional on the type

    Because of this, the PR should be treated as a breaking change.

@gjtorikian gjtorikian requested review from a team as code owners June 17, 2026 00:56
@gjtorikian gjtorikian requested a review from cmatheson June 17, 2026 00:56
@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR regenerates the Organizations API module from the OpenAPI spec via oagen, introducing breaking signature changes (positional → object params, organization key → id), converting createdAt/updatedAt from ISO strings to Date objects, and adding getAuditLogConfiguration as a new method.

  • All five organization methods have updated signatures and are documented as breaking changes; path params now use encodeURIComponent, and new type-safe option interfaces are generated for each endpoint.
  • The new update-organization.serializer.ts defaults omitted metadata and externalId to null instead of undefined, which means partial update calls (e.g. updating only name) will send \"metadata\": null and \"external_id\": null in the request body — clearing those fields on the server.
  • UpdateOrganization.stripeCustomerId no longer accepts null, removing the only way to clear a Stripe customer ID, along with its previously passing test.

Confidence Score: 3/5

The update path has a data-loss bug: any partial org update will silently clear metadata and externalId on the server. Needs fixes before merging.

The serializeUpdateOrganization serializer defaults omitted metadata and externalId to null instead of omitting them, so a routine call like updateOrganization({ id, name }) will send metadata: null to the API and wipe existing org metadata. A parallel issue removes null support from stripeCustomerId, making it impossible to clear the Stripe customer ID that was previously a tested feature.

src/organizations/serializers/update-organization.serializer.ts and src/organizations/interfaces/update-organization.interface.ts need fixes before this ships.

Important Files Changed

Filename Overview
src/organizations/serializers/update-organization.serializer.ts New serializer sends metadata: null and external_id: null when those fields are omitted, silently clearing existing org data on every partial update call.
src/organizations/interfaces/update-organization.interface.ts Drops null from stripeCustomerId, removing the ability to clear a Stripe customer ID — a previously tested and documented feature.
src/organizations/organizations.ts Main Organizations class regenerated from OpenAPI spec; method signatures migrated to object-param style and encodeURIComponent added on all path params. Idempotency key support removed from createOrganization.
src/organizations/serializers/organization.serializer.ts Deserialization updated to convert created_at/updated_at to Date objects; defaults metadata to {} and externalId to null consistent with prior behavior.
src/organizations/interfaces/organization.interface.ts createdAt/updatedAt changed from string to Date; allowProfilesOutsideOrganization, externalId, and metadata made optional — all documented breaking changes.
src/organizations/interfaces/audit-log-configuration-log-stream.interface.ts New interface for log stream; lastSyncedAt kept as `string
src/organizations/organizations.spec.ts Test suite regenerated; many previously-tested edge cases (idempotency key, Stripe customer ID clearing, error scenarios) are now absent.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant C as Caller
    participant O as Organizations class
    participant S as Serializer
    participant API as WorkOS API

    C->>O: "updateOrganization({ id, name })"
    O->>S: "serializeUpdateOrganization({ name })"
    Note over S: metadata: undefined ?? null → null
    S-->>O: "{ name, metadata: null, external_id: null }"
    O->>API: PUT /organizations/:id
    Note over API: null fields clear server state
    API-->>O: Organization (metadata cleared)
    O->>C: deserializeOrganization(data)

    C->>O: "getOrganization({ id })"
    O->>API: GET /organizations/:id
    API-->>O: OrganizationResponse
    O->>C: Organization (createdAt: Date)

    C->>O: "getAuditLogConfiguration({ id })"
    O->>API: GET /organizations/:id/audit_log_configuration
    API-->>O: AuditLogConfigurationResponse
    O->>C: AuditLogConfiguration
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant C as Caller
    participant O as Organizations class
    participant S as Serializer
    participant API as WorkOS API

    C->>O: "updateOrganization({ id, name })"
    O->>S: "serializeUpdateOrganization({ name })"
    Note over S: metadata: undefined ?? null → null
    S-->>O: "{ name, metadata: null, external_id: null }"
    O->>API: PUT /organizations/:id
    Note over API: null fields clear server state
    API-->>O: Organization (metadata cleared)
    O->>C: deserializeOrganization(data)

    C->>O: "getOrganization({ id })"
    O->>API: GET /organizations/:id
    API-->>O: OrganizationResponse
    O->>C: Organization (createdAt: Date)

    C->>O: "getAuditLogConfiguration({ id })"
    O->>API: GET /organizations/:id/audit_log_configuration
    API-->>O: AuditLogConfigurationResponse
    O->>C: AuditLogConfiguration
Loading

Reviews (1): Last reviewed commit: "Generate organizaions APIs using oagen" | Re-trigger Greptile

Comment on lines +20 to +21
metadata: model.metadata ?? null,
external_id: model.externalId ?? null,

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.

P1 Null defaults silently clear existing org data on every partial update

metadata: model.metadata ?? null and external_id: model.externalId ?? null mean that whenever a caller omits those fields — e.g. updateOrganization({ id, name: 'New Name' }) — the serializer emits "metadata": null and "external_id": null in the JSON body. The old test confirms the WorkOS API interprets explicit null as "clear this field" (see the deleted stripe_customer_id: null test that verified subject.stripeCustomerId became undefined). The previous serializer passed metadata: options.metadata, which serialized to undefined and was dropped by JSON.stringify, leaving server state unchanged. The fix is to pass the raw values without the ?? null fallback so unset fields are omitted from the request body entirely.

/** The domains associated with the organization, including verification state. */
domainData?: OrganizationDomainData[];
/** The Stripe customer ID associated with the organization. */
stripeCustomerId?: 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.

P1 stripeCustomerId: null support silently dropped

stripeCustomerId?: string no longer accepts null, which was the documented mechanism to clear a Stripe customer ID (updateOrganization({ organization: id, stripeCustomerId: null })). The deleted test 'clears the organization's Stripe customer ID with a null value' proves this was a supported, tested flow. Users who rely on clearing the Stripe customer ID will now get a TypeScript error and have no API alternative. The type should remain stripeCustomerId?: string | null, and the corresponding UpdateOrganizationResponse.stripe_customer_id wire type on line 33 needs the same fix.

Comment on lines 68 to +75
* @throws {ConflictException} 409
* @throws {UnprocessableEntityException} 422
*/
async createOrganization(
payload: CreateOrganizationOptions,
requestOptions: CreateOrganizationRequestOptions = {},
options: CreateOrganizationOptions,
): Promise<Organization> {
const { data } = await this.workos.post<OrganizationResponse>(
'/organizations',
serializeCreateOrganizationOptions(payload),
requestOptions,
);

const payload = options;
const { data } = await this.workos.post<

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.

P2 Idempotency key support removed from createOrganization

The previous signature accepted a second requestOptions: CreateOrganizationRequestOptions parameter carrying an idempotencyKey. This was the only safe way to retry a createOrganization call without risk of creating duplicate organizations on network failure. The PR drops this parameter entirely and removes the corresponding test. Is this intentional? If so, is there a replacement mechanism? Without idempotency key support, callers can no longer safely retry failed creates.

/** The current state of the Audit Log Stream. */
state: AuditLogConfigurationLogStreamState;
/** ISO-8601 timestamp of when the last event was successfully synced, or null if no events have been synced. */
lastSyncedAt: string | null;

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.

P2 lastSyncedAt stays a raw string while createdAt is a Date

createdAt: Date is deserialized via new Date(response.created_at) in the log-stream serializer, but lastSyncedAt: string | null is passed through as-is. Both fields hold ISO 8601 timestamps; keeping one as a raw string while the other is a Date is inconsistent and surprising for consumers. Consider converting lastSyncedAt to Date | null for consistency.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@gjtorikian gjtorikian marked this pull request as draft June 18, 2026 19:06
@gjtorikian gjtorikian added the autogenerated Autogenerated code or content label Jun 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autogenerated Autogenerated code or content

Development

Successfully merging this pull request may close these issues.

1 participant