Skip to content

feat(js): public user-type accessors on SailsProgram and SailsService#1343

Merged
vobradovich merged 4 commits into
masterfrom
feat/js-public-user-type-accessors-1320
May 12, 2026
Merged

feat(js): public user-type accessors on SailsProgram and SailsService#1343
vobradovich merged 4 commits into
masterfrom
feat/js-public-user-type-accessors-1320

Conversation

@ukint-vs
Copy link
Copy Markdown
Member

@ukint-vs ukint-vs commented May 7, 2026

Closes #1320.

Summary

API additions (sails-js v2)

  • Adds SailsProgram.programTypes: ReadonlyMap<string, Type> for program-level user types declared in the IDL's program {…} block.
  • Adds SailsService.types: ReadonlyMap<string, Type> for the service's own types {…} block (declared-only — does not include types inherited via extends).
  • Re-exports the v2 IDL AST typings (Type, TypeDecl, ITypeStruct, ITypeEnum, ITypeAlias, IIdlDoc, IServiceUnit, …) from the top-level sails-js entry, so consumers can import type { Type } from 'sails-js' without the sails-js/types subpath.

Both maps are eager-built in the constructor and return an empty Map when the corresponding IDL block is absent. They are typed ReadonlyMap to block .set() at the type level; runtime mutation is not enforced.

For the merged extends-chain scope, consumers continue to use program.resolveInService(name, decl) or walk service.extends[base].types. The new service.types accessor intentionally does not surface inherited types — making each service's declared surface explicit and keeping the existing resolution semantics untouched.

Why

vara-wallet and other downstream consumers were reaching into SailsProgram._doc via as any to enumerate user types (see #1320 for the full writeup). That escape hatch breaks silently the moment a private field is renamed between betas, and consumers couldn't stay type-safe at the AST level because sails-js-types was only reachable through the sails-js/types subpath.

Test Coverage

10 new tests in js/test/idl-v2-types-accessors.test.ts covering every code path on the new surface:

[+] SailsProgram.programTypes
  ├── [★★★ TESTED] populated from program.types
  ├── [★★★ TESTED] empty Map when program block has no types
  ├── [★★★ TESTED] empty Map when no program block (services-only IDL)
  └── [★★★ TESTED] same Map instance on repeated access (eager build)

[+] SailsService.types
  ├── [★★★ TESTED] populated from service.types
  ├── [★★★ TESTED] empty Map when service has no types block
  ├── [★★★ TESTED] declared-only: types in service A absent from service B.types
  ├── [★★★ TESTED] declared-only: base types not pulled into derived via extends
  │                  + sanity check: child.extends.Base.types includes the base type
  │                  + sanity check: program.resolveInService still merges scope
  └── [★★★ TESTED] same Map instance on repeated access (eager build)

[+] Top-level type re-export
  └── [★★★ TESTED] `import type { Type, ITypeStruct } from 'sails-js'` typechecks

COVERAGE: 10/10 paths tested (100%)

Tests: 14 → 15 (+1 new file, 10 new test cases). 84 of 84 v2 tests pass on the merged state (10 new + 74 adjacent regression-coverage suites: idl-v2-parser-type-resolver, idl-v2-type-resolver, event-type-shape, route-idx-header-validation, partial-idl).

Pre-Landing Review

Five review passes ran before landing. All findings resolved.

Pass Findings Resolution
/plan-eng-review 4 design tradeoffs (F1 empty-program contract, F2 lazy-vs-eager, F3 test placement, F4 mutation contract honesty) All 4 resolved as documented in the plan file.
/review (gstack pre-landing) 1 INFORMATIONAL (sails-js-types resolution) Tracked in #1344 at the time, then fully closed for this PR by Codex's later finding.
/code-review-graph review-pr Graph reported "high risk" (488-node blast radius) False signal — graph counts transitive dependents, not behavioral delta. Real delta is additive (zero existing surface modified).
/simplify (3 parallel reviewers) 1 actionable: ISailsService interface missing types member Fixed in d47b2e95.
/codex review 1 P1: export type * from 'sails-js-types' ships unresolvable external module ref Fixed in c4696f41 by re-exporting from the bundled ./types.js instead. Issue #1344 updated to reflect 1/4 of the historical leaks closed; 3 pre-existing references remain in lib/sails-idl-v2.d.ts, lib/util.d.ts, lib/parser.d.ts and will be addressed by the build-side fix tracked in #1344.

Bot comment from gemini-code-assist[bot] (eager-vs-lazy Map build) replied with the F2 rationale; no code change needed (review thread r3201667845).

Plan Completion

Item from plan Status
Add SailsProgram.programTypes getter DONE — js/src/sails-idl-v2.ts:259
Add SailsService.types getter DONE — js/src/sails-idl-v2.ts:502
Add types to ISailsService interface DONE — js/src/sails-idl-v2.ts:25
Top-level export type * from sails-js DONE — js/src/index.ts:12 (via ./types.js)
New test file: idl-v2-types-accessors.test.ts DONE — 10 tests
CHANGELOG ## Unreleased entries DONE — 2 bullets added
JSDoc with declared-only contract on service.types DONE
JSDoc with empty-Map contract on programTypes DONE

COMPLETION: 8/8 DONE. No PARTIAL, no NOT DONE.

Documentation

js/CHANGELOG.md updated under ## Unreleased with two bullets covering the new accessors and the top-level type re-export (including the resolution path via bundled ./types.js). No README sync needed — the existing README documents only the sails-js/types subpath and that path still works exactly as before; the top-level alias is purely additive.

No version bump: js/package.json stays at 0.5.1 since this is part of the next unreleased.

Test plan

  • pnpm test test/idl-v2-types-accessors.test.ts — 10/10 pass
  • pnpm test for adjacent v2 suites — 74/74 pass (no regressions)
  • pnpm build — clean; lib/index.d.ts emits export type * from './types.js'; lib/types.d.ts is fully dts-bundled (241 lines, all 49 AST types inlined, zero external module references)
  • Type-level smoke check: import type { Type, ITypeStruct } from 'sails-js' typechecks at consumer install time without sails-js-types in node_modules
  • CI: lint + test green on every commit
  • Codex P1 (sails-js-types external ref in lib/index.d.ts) — fixed in this PR
  • Pre-existing unrelated leaks in lib/sails-idl-v2.d.ts, lib/util.d.ts, lib/parser.d.ts — out of scope for this PR, tracked in sails-js: published .d.ts files reference private sails-js-types module #1344

🤖 Generated with Claude Code

Closes #1320.

Adds `SailsProgram.programTypes` and `SailsService.types`, both
`ReadonlyMap<string, Type>`, so consumers can enumerate user types
declared in the v2 IDL without reaching into the private `_doc` field
via `as any`. `SailsService.types` is declared-only — it returns the
service's own `types {…}` block. For the merged extends-chain scope,
`program.resolveInService(name, decl)` and `service.extends[base].types`
already cover the use cases. Both maps are empty when the corresponding
IDL block is absent.

Also re-exports the v2 IDL AST typings (`Type`, `TypeDecl`, `ITypeStruct`,
`ITypeEnum`, `ITypeAlias`, `IIdlDoc`, `IServiceUnit`, etc.) from the
top-level `sails-js` entry via `export type * from 'sails-js-types'`,
so consumers can `import type { Type } from 'sails-js'` without the
`sails-js/types` subpath. The internal `sails-js-types` workspace
package stays `private: true`.

The new maps are built eagerly in the constructors. They are typed
`ReadonlyMap` to block `.set()` at compile time; runtime mutation is
not enforced.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces public ReadonlyMap accessors for user types in SailsProgram and SailsService, and re-exports IDL AST types from the package root. A review comment suggests adopting a lazy indexing strategy for these type maps to improve performance and maintainability by avoiding unnecessary initialization overhead in the constructor.

Comment thread js/src/sails-idl-v2.ts
@ukint-vs ukint-vs requested a review from vobradovich May 7, 2026 13:13
ukint-vs and others added 3 commits May 7, 2026 17:16
The new `SailsService.types` getter is reachable on the class but was
missing from the `ISailsService` read-surface interface. Surfaced by
/simplify code-quality review as a leaky abstraction (the interface
lists `functions`, `queries`, `events`, `extends`, `routeIdx` — `types`
belongs there too).

Two-line interface addition. No runtime change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…package

The previous form, `export type * from 'sails-js-types'`, emitted an
unresolvable external module reference into the published `lib/index.d.ts`:
`sails-js-types` is `private: true` and only a devDependency of `sails-js`,
so downstream TypeScript consumers could fail to resolve the root
declaration even for normal value imports like `import { SailsProgram }
from 'sails-js'`.

Re-export from `./types.js` instead. That path resolves at consumer
install time to the rollup-`dts`-bundled `lib/types.d.ts`, which already
inlines every `sails-js-types` interface as a self-contained declaration.
No external module reference, no devDependency leak.

Caught by `codex review` as [P1].

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vobradovich vobradovich merged commit 8f7e558 into master May 12, 2026
2 checks passed
@vobradovich vobradovich deleted the feat/js-public-user-type-accessors-1320 branch May 12, 2026 09:51
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.

sails-js v2: expose public accessors for program-level and per-service user types

2 participants