Skip to content

fix(types): type listOrganizations result as a keyed map, not an array#667

Merged
John-David Dalton (jdalton) merged 1 commit into
mainfrom
fix/org-list-plan-types
Jul 25, 2026
Merged

fix(types): type listOrganizations result as a keyed map, not an array#667
John-David Dalton (jdalton) merged 1 commit into
mainfrom
fix/org-list-plan-types

Conversation

@jdalton

Copy link
Copy Markdown
Collaborator

What

Fixes two result-type correctness bugs on the listOrganizations() result type, surfaced during the socket-vscode SDK migration (task #65).

  1. organizations is a map, not an array. GET /v0/organizations returns organizations as an object keyed by org id (OpenAPI additionalProperties). OrganizationsResult declared it as OrganizationItem[]. socket-vscode had to reach for Object.values() to stay correct. The strict type is now Record<string, OrganizationItem>.
  2. plan is an open-ended string. The task flagged the org plan field as a narrow 3-value union that needed widening. In the current main it is already typed as string (both the OpenAPI schema and the generated types agree), so there was nothing to widen. New tests lock that in so a future narrowing regresses loudly.

Breaking change

This is a breaking type change: consumers that treated organizations as an array will now get type errors. The runtime was always a map, so anything relying on the array type was already broken at runtime — this change makes the compiler catch it.

Fixed at the generator source

src/types-strict.mts is auto-generated from the OpenAPI spec. The fix was made in the generator's emit template (scripts/repo/generate-strict-types-emit.mts) and the generated artifact regenerated to match — not hand-patched downstream.

Root cause, before/after, and how types are produced

How the types are produced

src/types-strict.mts carries a banner: AUTO-GENERATED from OpenAPI definitions using AST parsing - DO NOT EDIT MANUALLY. It is produced by scripts/repo/generate-strict-types.mts, which reads the local openapi.json, runs openapi-typescript over it, and emits strict wrapper types via the template functions in scripts/repo/generate-strict-types-emit.mts. The OrganizationsResult wrapper is emitted by generateWrapperTypes() in that emit file, so that is the correct place to fix it.

OpenAPI source of truth

The /organizations GET 200 schema:

"organizations": {
  "type": "object",
  "additionalProperties": { /* org object with id, name, image, plan, slug */ }
}

additionalProperties means a map keyed by org id — a Record, not an array. plan is { "type": "string" }, i.e. already open-ended.

Before / after (strict type)

 export type OrganizationsResult = {
   cause?: undefined
   data: {
-    organizations: OrganizationItem[]
+    organizations: Record<string, OrganizationItem>
   }
   error?: undefined
   status: number
   success: true
 }

The generator has a separate, pre-existing path bug (the fleet migration moved these scripts into scripts/repo/, but getRootPath() still only walks up one level, so it looks for openapi.json under scripts/ instead of the repo root). Running the generator in-place therefore fails today. I validated the template change by running the generator against the OpenAPI at the path it expects, confirmed it emits Record<string, OrganizationItem>, then applied that exact change to the committed generated artifact preserving its existing format. Fixing the generator's path resolution is out of scope for this change.

JSDoc example

The listOrganizations() doc example iterated with result.data.organizations.forEach(...), which is invalid for a map. Updated to Object.values(result.data.organizations).forEach(...).

Tests

  • Rewrote the OrganizationsResult runtime test in test/repo/unit/types-strict.test.mts to mock the real map shape (previously it mocked an array — which never occurs at runtime), asserting map-keyed access via Object.values() / Object.keys() and key lookup.
  • Added a compile-time (type-level) test locking organizations to a keyed Record and plan to an open string (not a narrow union). The block is validated by the type checker.

Verification

All run on Node 24.18.0:

Command Result
pnpm run type (tsgo --noEmit) pass
pnpm run lint (oxlint + oxfmt) pass
vitest run .../types-strict.test.mts 7 passed
pre-commit hook (staged lint + tests) 10 passed
pnpm run build pass (dist declarations show the Record shape)

GET /v0/organizations returns `organizations` as a map keyed by org id
(OpenAPI `additionalProperties`), but OrganizationsResult declared it as
OrganizationItem[]. Consumers had to work around the mismatch (e.g.
socket-vscode used Object.values()). Fix the strict type to
Record<string, OrganizationItem> at its generator source
(generate-strict-types-emit.mts) and regenerate types-strict.mts.

The org plan field is already typed as an open-ended string in both the
OpenAPI schema and the generated types; tests now lock that in alongside
the map shape so a future narrowing regresses loudly.

This is a breaking type change, but the runtime was always a map, so any
consumer relying on the array type was already broken at runtime.
@jdalton
John-David Dalton (jdalton) merged commit 29a1dae into main Jul 25, 2026
6 checks passed
@jdalton
John-David Dalton (jdalton) deleted the fix/org-list-plan-types branch July 25, 2026 16:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant