Sitelet https://github.com/sdcorejs/sdcorejs-angular/pull/64
Skip to content

chore(deps): upgrade @sdcorejs/utils to 1.2.3 on every line - #64

Merged
sdcorejs merged 1 commit into
mainfrom
chore/utils-1.2.3
Sep 29, 2026
Merged

sdcorejs merged 1 commit into
mainfrom
chore/utils-1.2.3

Conversation

@sdcorejs

Copy link
Copy Markdown
Owner

Summary

This upgrades @sdcorejs/utils from 1.1.4 to 1.2.3 in v19 (canonical), rolls it out to v20/v21/v22 with npm run sync, and updates the showcase. 1.2.3 is the first 1.2 release whose legacy date helpers accept backend timestamps again (2026-07-09T08:49:29.851409Z, +0700); with 1.2.0–1.2.2, Core Legacy rendered those table dates as --.

Each lockfile changes only the @sdcorejs/utils entry (1.2.3 drops its optional rxjs peer). Every lockfile keeps its original indentation. Installs follow the repo policy on Node 22.22.3: --legacy-peer-deps for v19/v20/v21 and the showcase, a clean install for v22.

1.2 contract changes and how this PR absorbs them

1.2 change Effect without a fix Fix
normalizeAsync returns a structural SubscribableLike toSignal (auth) and defer/combineLatest (breadcrumb) no longer compile local adapter from MaybeAsync to a real RxJS Observable (same as Core Legacy); re-emitting sources stay live
getNestedValue returns T | undefined TableFormatService stops compiling one documented SdTableCellValue alias, as in Core Legacy
BrowserUtilities.upload rejects FilePickerCancelledError on cancel or timeout sd-upload-file shows an error toast when the user cancels; SdApiService.upload() and SdExcelService.upload() throw cancellation is caught: no toast, undefined, and { items: [], file: null } respectively; validation errors are unchanged
MaybeAsync<T> no longer includes RxJS Observable<T> consumer compile break: Observable<string> for string | null | undefined, an Observable of a subtype, or an Observable of one union member is rejected (the showcase's BehaviorSubject<string> breadcrumb label failed) public async slots are typed MaybeAsync<T> | Observable<T>, which is what 1.1.x accepted; internal resolveMaybeAsync calls pin their type argument

The slots widened are: sd-breadcrumb label; auth signout, changePassword and authInfo; layout sidebar and userInfo factories; permission loadPermissions and getToken.

Tests

  • Typed (satisfies) regression specs for breadcrumb, auth, layout and permission. I checked with a tsc probe that these assignments fail against the unwidened MaybeAsync of 1.2.3.
  • Cancel and validation-error specs for SdApiService.upload, SdExcelService.upload and sd-upload-file.
  • A sdFormatDatetime spec for microsecond, nanosecond and colon-less-offset timestamps, as a guard for future utils bumps.

Verification

This branch is rebased on main, so it includes #63.

  • npm run check:sync: passes.
  • tsc -p tsconfig.spec.json on v19/v20/v21/v22, and tsc on the showcase: 0 errors.
  • v19 and v22 ng test sdcorejs-angular --code-coverage (Node 22.22.3): 5954/5954 each, thresholds met.
  • v19 ng build sdcorejs-angular, npm run test:scripts, and eslint on the changed files: no new findings.
  • Release note: dependencies changes for every target, so the release-contract snapshot for the next suffix (scripts/release-contracts/3.0.json) has to record this reviewed dependency change when it is created.

🤖 Generated with Claude Code

Move the runtime dependency from 1.1.4 to 1.2.3 in v19 (canonical) and roll
it out to v20/v21/v22 and the showcase. 1.2.3 is the first 1.2 release whose
legacy date helpers accept backend timestamps with sub-millisecond fractions
and colon-less offsets again. With 1.2.0-1.2.2, Core Legacy rendered every
such table date as "--".

Lockfiles change only the @sdcorejs/utils entry (1.2.3 drops its optional
rxjs peer). Each lockfile keeps its original indentation, v19/v20/v21 and the
showcase were installed with --legacy-peer-deps, and v22 with a clean install,
all on Node 22.22.3.

Adapting to the 1.2 contract without changing @sdcorejs/angular behaviour:

- normalizeAsync now returns a structural subscribable, which toSignal and
  defer/combineLatest reject. auth.service and sd-breadcrumb adapt MaybeAsync
  to a real Observable (same adapter as Core Legacy), so re-emitting sources
  stay live.
- getNestedValue returns T | undefined; TableFormatService types cell values
  through one documented SdTableCellValue alias, as Core Legacy does.
- BrowserUtilities.upload now rejects with FilePickerCancelledError on cancel
  or timeout instead of resolving empty. sd-upload-file no longer shows an
  error toast for it, SdApiService.upload() still resolves undefined without
  a request, and SdExcelService.upload() still resolves an empty result.
  Validation errors are unchanged.
- MaybeAsync<T> is now T | PromiseLike<T> | SubscribableLike<T> instead of
  including RxJS Observable<T>. SubscribableLike does not accept an Observable
  of a narrower type: Observable<string> for string | null | undefined, an
  Observable of a subtype, or an Observable of one union member. That broke
  consumer code such as the showcase's BehaviorSubject<string> breadcrumb
  label. The public async slots (breadcrumb label, auth signout /
  changePassword / authInfo, layout sidebar / userInfo, permission
  loadPermissions / getToken) are typed MaybeAsync<T> | Observable<T>, which
  restores the 1.1.x acceptance, and internal resolveMaybeAsync calls pin
  their type argument.

Regression specs cover each point, including typed (satisfies) configs that
fail to compile against the unwidened types and a datetime pipe spec for
microsecond timestamps. Docs for auth, layout, permission, breadcrumb and
query-bar plus CHANGELOG [Unreleased] are updated.

Verified: check:sync; tsc on v19/v20/v21/v22 and the showcase; v19 and v22
suites with coverage (5936/5936 each); v19 library build; test:scripts;
eslint on the changed files reports nothing new.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sdcorejs
sdcorejs merged commit 3fcd2a8 into main Sep 29, 2026
5 of 6 checks passed
sdcorejs added a commit that referenced this pull request Sep 30, 2026
…bump @sdcorejs/utils to 1.2.4 (#65)

* chore(deps): bump @sdcorejs/utils to 1.2.4 on every line

1.2.4 fixes the root cause of the Observable typing break upstream
(sdcorejs-utils #14). SubscribableLike.subscribe no longer includes null
in its first parameter. The null had blocked TypeScript's contravariant
check against RxJS's single-argument overload, so an Observable of a
narrower type (Observable<string> for string | null | undefined, a
subtype, one union member) was rejected as MaybeAsync<T>. It now fits in
application code too, not only in the Core slots widened in #64.

The explicit MaybeAsync<T> | Observable<T> slots from #64 stay: redundant
with 1.2.4 but harmless, and they keep the 1.1.x contract visible in the
public types. Each lockfile (v19-v22, showcase) changes only the
@sdcorejs/utils entry and keeps its original indentation; v22 stays LF.
CHANGELOG [Unreleased] and sd-query-bar.md are updated.

Verified: check:sync; tsc on v19/v20/v21/v22 and the showcase; v19
library build; v19 suite with coverage 5952/5954. The two failures are
SdImageEditor canvas export specs, which pass alone (114/114) and do not
use @sdcorejs/utils.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(api): API handlers from lazy routes (provideSdApiConfiguration) and longest-prefix selection

SdHttpInterceptor is registered at the root injector through
HTTP_INTERCEPTORS and resolved inject(SD_API_CONFIG) once, at construction;
SdApiService did the same. A library that provides
{ provide: SD_API_CONFIG, multi: true } in an NgModule loaded with
loadChildren puts it in the lazy route's child injector, which the root
interceptor never sees. Every handler was lost in silence: no headers, no
beforeRemote/afterRemote, no error toast. One Portal hit this with four
lazy libraries and had to re-register every library configuration at root.

- SdApiHandlerRegistry (providedIn root) holds runtime registrations as a
  signal. register() returns an idempotent unregister.
- provideSdApiConfiguration(Class | value): EnvironmentProviders registers
  on scope creation through provideEnvironmentInitializer and unregisters
  through the scope's DestroyRef. A class is provided inside the scope so it
  can inject scoped dependencies. The same declaration works in root
  providers, an eager NgModule, a lazy Route.providers and a loadChildren
  module. It is exported from @sdcorejs/angular/services/api and covered by
  public-api.spec.
- The interceptor and SdApiService resolve per request from the static
  SD_API_CONFIG list plus the registry. Root providers behave as before, and
  SdApiModule still does not call provideHttpClient.
- Behaviour change: among matching handlers, the longest matching host
  prefix (origin + path, via sdMatchesSecureRoute) wins instead of the first
  in registration order; ties keep order, root first. Host matching itself
  is unchanged.

The token alias and blank-host handling (steps D and E) already existed
here. Core Legacy 19.0.41 ships the same registry API and longest-prefix
rule but keeps raw startsWith matching; the CHANGELOG records the
difference.

Specs (api-handler-registry.spec.ts) cover:
- a lazy Route.providers handler after activation;
- a loadChildren NgModule class configuration with a scoped dependency;
- no interception after the scope injector is destroyed;
- root and lazy coexisting with the longest prefix winning, in both orders;
- tie order;
- SdApiService mapResponse from a lazy handler;
- the SD_API_CONFIG / SD_API_CONFIGURATION token alias;
- blank hosts.

Rolled out from v19 to v20/v21/v22 with npm run sync; v22 stays LF.

Verified:
- npm run check:sync: passes.
- npm run lint:release on Node 22.22.3: all four lines pass.
- ng test --code-coverage in v19, v20, v21 and v22: 5964/5964 each,
  exit 0.
- npm run test:scripts: 225/225.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(api): pick up SD_API_CONFIG from lazy route injectors without library changes

SdApiHandlerRegistry now reads the SD_API_CONFIG entries of every injector the
Router creates for a route (Route.providers, or the NgModule of loadChildren).
It walks router.config when the registry is created and on each
RoutesRecognized, so registration happens in the same navigation, before
guards, resolvers and components. Each injector is read once, with
{ self: true }, and its entries are removed when the injector is destroyed,
including by the Angular 21+ router injector cleanup.

Libraries keep their plain { provide: SD_API_CONFIG, useClass, multi: true }
provider and do not need to know whether the shell mounts them eagerly or
lazily. provideSdApiConfiguration stays for values and for scopes the Router
does not create. Rolled out to v20, v21 and v22.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: nghiatt15_onemount <nghiatt15@onemount.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants