Changelog
This project follows Keep a Changelog and
Semantic Versioning. Releases up to 3.8.0
were published as ng-qubee.
[Unreleased]
Section titled “[Unreleased]”[4.0.0] - 2026-10-22
Section titled “[4.0.0] - 2026-10-22”The package is renamed, the framework-agnostic half moves into @qubeejs/core, RxJS is gone, and lists can keep their state in the URL. A migration table closes the section.
Breaking
Section titled “Breaking”- Package renamed to
@qubeejs/angular: the Angular adapter now lives beside@qubeejs/coreunder the@qubeejsscope.ng-qubee@3.xstays on npm unchanged. The build output moves todist/qubeejs/angular; CI, coverage and publish paths follow. - Built on
@qubeejs/core(^1.6.1, new peer dependency): every driver, strategy, enum, error, model and type that used to live in this repository is now imported from the core and re-exported unchanged, soimport { StrapiRequestStrategy, SortEnum, UnsupportedFilterError } from '@qubeejs/angular'keeps resolving. The core’s parity suite compares every driver’s URIs byte for byte againstng-qubee@3.8.0; they are identical except where 3.x was wrong, listed in the next entry. The duplicated drivers, strategies, models, enums, errors and their specs leave this repository; the core’s own suite covers them, and the specs kept here cover the Angular surface — DI wiring, signals, scoping. - Generated URIs differ from 3.8.0 where 3.x was wrong — fixes the core shipped in 1.1 and 1.4:
- Filter values and search terms are percent-encoded. No 3.x driver encoded them, so a
&in a value split it into a new parameter, a#cut off the rest of the URI — pagination included — and user input could inject parameters of its own. Keys, operator syntax and the,joining several values stay literal. If you calledencodeURIComponent()on values yourself, remove it: it now double-encodes. - PocketBase joins clauses with
%26%26instead of a raw&&, which reached the server cut off at the first&; itsSWwildcard is%25. PostgRESTCONTAINSuses its documented*wildcard instead of a bare%. - Exactly one slash joins the base URL and the resource:
setBaseUrl('https://api.dev/')no longer giveshttps://api.dev//articles, and a resource with a leading slash no longer gives a protocol-relative//projects/42/tasks. - On JSON:API and Spatie,
addFields()on a nested resource (users/42/followers) sends the fields as given instead of throwingKeyNotFoundError.
- Filter values and search terms are percent-encoded. No 3.x driver encoded them, so a
- New peer dependencies:
@angular/common>=17.0.0(every Angular application has it; the lists use itsDOCUMENT) and, optional,@angular/router>=17.0.0— needed only by the@qubeejs/angular/routerentry point. - RxJS removed: no longer a peer dependency, no longer imported anywhere.
QubeeQueryBuilder.generateUri()returns the URI synchronously as astringand throws on an invalid state, instead of returning anObservable<string>; theuri$Observable is replaced by theurisignal (see Added). - Driver definitions replace
DriverEnumin the config:provideQubee({ driver: STRAPI_DRIVER })instead of{ driver: DriverEnum.STRAPI }, mirroring the core’screateQubee(). Importing one definition lets the other nineteen drivers tree-shake out of the bundle — a single driver is about 2.7 kB gzipped against 10.8 kB for the whole registry. For a backend chosen at runtime,provideQubee({ driver: DRIVERS[id] })keeps working and makes the cost visible at the import site.IConfigbecomes the core’sQubeeConfig; theQUBEE_DRIVERtoken now holds theDriverDefinition(read.idfor the identifier). - The
Ngprefix and theServicesuffix are gone:NgQubeeService→QubeeQueryBuilder,PaginationService→QubeePaginator,NestService→QubeeSignalStore,NgQubeeModule→QubeeModule,provideNgQubee→provideQubee,provideNgQubeeInstance→provideQubeeInstance,buildNgQubeeProviders→buildQubeeProviders,NG_QUBEE_*→QUBEE_*. Each Angular class is named after the core class it wraps, with the brand as prefix, so the rule is short: unprefixed is the core (QueryBuilder,Paginator,QubeeStore),Qubee-prefixed is the Angular injectable with signals. DroppingServicefollows the current Angular style guide. NestServicereplaced byQubeeSignalStore: an@Injectable()subclass of the coreQubeeStorewhose single addition isstate: Signal<QueryBuilderState>. Thenestsignal becomesstate. Snapshots are deeply frozen by the core, which closes #81 — mutating whatstate()returns now throws instead of silently corrupting later reads.- Pagination readers are computed signals (#70):
currentPage,totalPages,isFirstPage,isLastPage,hasNextPage,hasPreviousPageandpaginationHeadersareSignals onQubeeQueryBuilder. They are still read asqb.hasNextPage(), so call sites compile unchanged, but they now re-evaluate only when the state changes and are tracked by templates,computed(),effect()andhttpResource().totalPages()returnsundefineduntil a response has synced the bounds, where the 3.x method threwPaginationNotSyncedError;lastPage()keeps throwing, for imperative navigation. - Type names lose the
Iprefix —IFields→Fields,IQueryBuilderState→QueryBuilderState,IPaginationConfig→PaginationConfig, and so on — following the core’s convention thatImarks only interfaces a class implements (IRequestStrategyandIResponseStrategy, both unchanged).IPaginatedObjectbecomesPaginatedObject, andpaginate()accepts the core’sRawResponse(Record<string, unknown> | readonly unknown[]), which finally types the bare-array bodies PostgREST and WordPress return. qsdropped: the core ships its own serialiser, verified againstqscase by case.@qubeejs/angularhas no runtime dependency beyondtslib.- Errors carry a
code,contextandcause(from the core’sQubeeErrorbase), and theUnsupported*Errormessages now name the active driver — “The ‘laravel’ driver does not support filters.” — instead of the previously hardcoded, and by now factually wrong, driver lists.
- Lists in the URL:
injectQubeeList(list)— the Angular side of the core’sdefineList(). Declare a list once (its resource, driver, page-URL params and how its state becomes builder calls) andinjectQubeeList()returnsstateandrequestas signals,isPending, andset(),setPage(),reset(),href()and — for a list with exactly onesortParam— a typedtoggleSort().stateupdates as soon asset()is called; the URL is written at most once per call, after an optionaldebounce, with defaults left out, foreign keys kept and the page reset on any other change.request({ uri, headers, paginate }) changes once per navigation and only when the URI or headers differ, sohttpResource()fetches once per page. The URL stays the source of truth: Back, Forward, a link elsewhere, a refused navigation or destroying the injector drop what was not yet written, and a link clicked while a debounce waits commits it first. A list that declares an input (a project id from the route path) requires it asinput— a value, or a signal, withnullmeaning “not ready” — and refuses one it does not declare, both at compile time. Behaviour matches@qubeejs/react’suseQubeeList. - List adapters, under the new
QUBEE_LIST_ADAPTERtoken:provideQubeeRouterAdapter()in the new@qubeejs/angular/routerentry point (the Angular Router; the list’s location moves to the incoming URL atResolveEnd, so a list injected in the component being navigated to starts from that URL),provideQubeeBrowserAdapter()(History API, for applications without a router), andprovideQubeeMemoryAdapter()/createQubeeMemoryAdapter()(state kept out of the address bar, per component or per subtree). Any{ location: Signal<ListLocation>, navigate(href, { replace }) }object is an adapter. Without one,injectQubeeList()throwsMissingRouterAdapterErrorrather than fall back to browser history behind a router’s back. Therouterandhttpentry points are declared intypesVersions, so applications still onmoduleResolution: "node"(the Angular 17 default) resolve their types. qubeeHttpResource(request)in the new@qubeejs/angular/httpentry point: fetches the page a list’srequestasks for —qubeeHttpResource<Article>(list.request)— and returns it parsed by the list’s own paginator asvalue: Signal<PaginatedCollection<T> | undefined>, withisLoading,error,reload()and the underlyingresource. Built onhttpResource(), so it goes throughHttpClientand your interceptors, aborts stale requests, and needs Angular 19.2 — hence its own entry point, which applications on 17 to 19.1 never load. It sends the request’s headers (PostgREST’sRange) and passes the response headers topaginate(), keeps the previous page on screen while the next one loads (keepPreviousData, defaulttrue; recorded in an effect, so it holds whether or not the page was read in between), fetches nothing for anullrequest, and takes extra request options (withCredentials,context, headers) asinit. The counterpart of@qubeejs/react’suseQubeeQuery.setParam(key, ...values)/deleteParams(...keys)onQubeeQueryBuilder: parameters no driver models, appended after the driver’s own on every driver; a key the driver already emits throwsParamCollisionError.QubeeConfig.baseUrl:provideQubee({ baseUrl, driver })prefixes every URI. Each store starts from it (exposed as theQUBEE_BASE_URLtoken),setBaseUrl()overrides it per instance andreset()clears it, as with the core’screateQubee().- Ransack (Rails) and FastAPI drivers (
RANSACK_DRIVER,FASTAPI_DRIVER), from the core, with a docs page each — twenty drivers in all. - The core’s list helpers, re-exported:
defineList(),readListState(),buildListHref(),buildListRequest(), the six params,toSearchParams(),getPageWindow(),getPageRange(),toggleSort(),getAriaSort()andPaginatedCollection.toPlain(). uri: Signal<string | undefined>onQubeeQueryBuilder: the URI for the current state, recomputed on every mutation,undefineduntil a resource is set.undefinedis the idle sentinelhttpResource()understands, sohttpResource(() => qb.uri())stays idle untilsetResource()and re-fetches on every filter, sort or page change with no glue code.state: Signal<QueryBuilderState>on bothQubeeSignalStoreandQubeeQueryBuilder.createQubeeInstance(injector?)(#69): a factory returning an isolated{ builder, paginator, store }— the Angular counterpart of the core’screateQubee()— for root-scoped services that cannot useprovideQubeeInstance(). The three objects are created in a child injector, so they inherit the driver, strategies and options fromprovideQubee()while owning private state; two root services that each call it never see each other’s filters or pages. Defaults to the current injection context, accepts an explicitInjectorfrom anywhere else, and needs no teardown. The return type is exported asQubeeInstance.- Change-aware signals: the store’s
statesignal compares snapshots structurally andpaginationHeaderscompares header maps by value, so a write that leaves the state unchanged —paginate()re-syncing the page it was asked for, say — notifies nothing. This is what keeps anhttpResource()bound touri()orpaginationHeaders()from re-fetching after every parsed response.
Changed
Section titled “Changed”- Angular peer range opened upward to
>=17.0.0, from>=17.0.0 <23.0.0. The floor stays at 17 because the library is built onsignal()andcomputed(), stable since that release, and uses nothing newer —inject(),DestroyRef,untracked()andmakeEnvironmentProviders()predate it;effect()andhttpResource()appear only in the docs. The ceiling goes because the package ships in partial compilation mode, so the consuming application links the declarations at its own version, and the 3.x cap only ever produced anERESOLVEon each new Angular major until a widening release shipped (3.8.0 was exactly that). In its place the CI matrix keeps verifying the floor (17), the middle (20) and the current stable (22) by installing the packed artifact into a freshly scaffolded application and building it, and gains anexperimentalleg on Angular’snextprerelease that reports early and never blocks. Verified for this release by installing the packed artifact into a freshly scaffolded Angular 17.3.12 application on Node 20 and an Angular 22.2.3 application on Node 24 and building both — each usinginjectQubeeList()through@qubeejs/angular/router, and the second also compiling the components from the Lists in the URL page verbatim. - Repository renamed to
AndreaAlhena/qubeejs-angular, matching the package. Badges,package.jsonmetadata, the docs edit links, the security policy and the changelog comparison links point at the new URL; GitHub redirects the old one. The docs site moves to qubeejs-angular.andreatantimonaco.me. - Docs site rebuilt on Astro Starlight in
docs/, replacing the Docusaurus site indocs-site/, in the design of the@qubeejs/coredocs with the Angular palette. The 4.0 docs cover only what the adapter adds — Getting started, Migrating from ng-qubee 3.x, The query builder, Pagination, Fetching data (qubeeHttpResource(), plushttpResource()andHttpClientpatterns for Angular 17 to 19.1), Lists in the URL and Per-component instances — and an API reference generated by TypeDoc for the adapter’s own exports. Eight recipes put the parts together end to end — two independent lists on one page, one builder per feature service, a debounced search box, pagination controls, sortable table headers, a list scoped by a route parameter, header-paginated backends and testing a list component — each sample compiled against the packed package. Drivers, filters, operators and sorting link to the core docs instead of being duplicated, so the per-driver pages leave the 4.0 docs. A version menu (starlight-versions) switches to the archived 3.8 docs, which keep describingng-qubeewith their driver pages and API reference; 3.3–3.7 are folded into 3.8, since those minors were additive. The changelog is published as a docs page generated from this file. Every Docusaurus URL redirects:/docs/…and/docs/next/…guide pages to the 4.0 guide, their driver pages to the core’s,/docs/3.3.0/…to/docs/3.7.0/…to the same page in the 3.8 archive, and the 3.x API pages, under any of those prefixes, to the 3.8 API reference by their exact old names.docs/README.mddocuments cutting a version at release time, which replacesdocusaurus docs:version. - Requires
@qubeejs/core1.6.1, whose PostgREST driver places a short last page on the page that was asked for: 1.6.0 sized a page by the span ofContent-Range, so30-33/34at a limit of 10 parsed, and synced into the store, as page 8 of 9.QubeePaginator.paginate()andqubeeHttpResource()need no change, since the core’sPaginatornow passes the requested page size to the response strategy; both gain a short-last-page spec (AndreaAlhena/qubeejs-core#45). - CI compat consumer rewritten against the v4 API, and now also bootstraps the Angular Router with
provideQubeeRouterAdapter()and usesinjectQubeeList(), failing if either is tree-shaken out — and, on the Angular 20, 22 andnextlegs,qubeeHttpResource(); artifact and coverage paths follow the rename. Lint covers therouter/andhttp/entry points, and the karma builder runs their specs. - Coding standards aligned with
@qubeejs/core: ESLint 10 withtypescript-eslint,angular-eslint,eslint-plugin-perfectionistandeslint-plugin-jsdocreplaces the old per-plugin setup, with the core’s rules —_required on private members and forbidden on the rest, explicit return types,import type, noany, JSDoc on every public declaration, and imports and class members sorted alphabetically (field initialisers that read each other keep their order). Prettier withprettier-plugin-sort-jsonformats the repository (npm run format,format:checkin CI and in the pre-commit hook).test/conventions.test.mjsports the core’s repository checks — one declaration kind per file, exported types only in*.type.ts, kebab-case filenames,UPPER_CASEonly for literal constants (andInjectionTokens), no RxJS — and runs onnode:testin the lint job (npm run test:conventions).QubeeHttpResourceOptionsandQubeeHttpResourceRefmove tohttp/src/types/qubee-http-resource.type.ts;@qubeejs/angular/httpexports them as before. No runtime change.
Migrating from ng-qubee 3.x
Section titled “Migrating from ng-qubee 3.x”| 3.x | 4.0 |
|---|---|
npm i ng-qubee |
npm i @qubeejs/angular @qubeejs/core |
import { … } from 'ng-qubee' |
import { … } from '@qubeejs/angular' |
provideNgQubee({ driver: DriverEnum.STRAPI }) |
provideQubee({ driver: STRAPI_DRIVER }) |
NgQubeeModule.forRoot({ driver: DriverEnum.STRAPI }) |
QubeeModule.forRoot({ driver: STRAPI_DRIVER }) |
provideNgQubeeInstance() |
provideQubeeInstance() |
inject(NgQubeeService) |
inject(QubeeQueryBuilder) |
inject(PaginationService) |
inject(QubeePaginator) |
NG_QUBEE_DRIVER (a DriverEnum), NG_QUBEE_REQUEST_STRATEGY, … |
QUBEE_DRIVER (a DriverDefinition), QUBEE_REQUEST_STRATEGY, … |
| driver chosen at runtime | provideQubee({ driver: DRIVERS[id] }) |
qb.generateUri().subscribe(uri => …) |
const uri = qb.generateUri(); — or read qb.uri() |
qb.uri$ |
qb.uri (signal; undefined until a resource is set) |
qb.uri$.pipe(switchMap(uri => http.get(uri))) |
httpResource(() => qb.uri()) |
inject(NestService).nest() |
inject(QubeeSignalStore).state() or qb.state() |
@angular/core peer >=17.0.0 <23.0.0 |
>=17.0.0 |
qb.totalPages() throwing before sync |
returns undefined before sync |
IConfig |
QubeeConfig |
IFields, IFilters, ISort, IOperatorFilter, IQueryBuilderState, IQueryBuilderConfig, IPaginationConfig, INormalized, IPaginatedObject, IStrategyCapabilities |
the same names without the I |
rxjs in peerDependencies |
not required by the library (Angular itself still peers it) |
addFilter('q', encodeURIComponent(term)) |
addFilter('q', term) — values are encoded for you |
setBaseUrl() after every reset() or in every instance |
provideQubee({ baseUrl, driver }) |
Everything else — the fluent mutators, paginate(), PaginatedCollection, the enums, the errors and the strategies — keeps its name and behaviour.
[3.8.0] - 2026-07-31
Section titled “[3.8.0] - 2026-07-31”- Angular 22 support (#77): the
@angular/corepeer range widens from>=17.0.0 <22.0.0to>=17.0.0 <23.0.0. Angular 22 applications previously could not install the package at all — npm rejected it withERESOLVE ... peer @angular/core@">=17.0.0 <22.0.0" from ng-qubee@3.7.0. No library source change was required: because ng-packagr emits partial-Ivy output whoseminVersionmarkers are12.0.0/14.0.0, the consuming application’s linker compiles the declarations at its own version, so the Angular version the package is built with and the versions it runs on are decoupled. Verified by installing the packed artifact into real Angular 17, 20 and 22 applications and completing a production build in each, with zero residualngDeclare*calls in the output bundles. - Angular version compatibility matrix in CI (#77): a new
compatjob packs the built artifact, scaffolds a genuine Angular application at the floor (17), the middle (20) and the ceiling (22) of the declared peer range, wiresprovideNgQubee()into it and builds it. It asserts both that the library survives into the bundle (not tree-shaken) and that no partial declarations remain unlinked, so the declared range stays continuously verified instead of verified once.
Changed
Section titled “Changed”- Dev toolchain moved to Angular 22 (#77):
@angular/*,@angular/cli,@angular-devkit/build-angular,@angular-eslint/*andng-packagrto^22.0.0;typescriptto~6.0.3(Angular 22 peers>=6.0 <6.1);@typescript-eslint/*to^8.65.0(the first line accepting TypeScript 6). TypeScript 6 required no source changes — all 127 files type-check clean, and none of TS 6.0’s deprecated options (target: ES5,moduleResolution: node10,baseUrl,outFile,downlevelIteration) are in use. The full 985-spec suite passes on the new toolchain. - Test bootstrap migrated off
@angular/platform-browser-dynamic(#77):src/test.tsnow initialises the test environment withBrowserTestingModule/platformBrowserTestingfrom@angular/platform-browser/testing. The old package is npm-deprecated and pulled the JIT compiler into the test bundle. Test-only change; the spec files already usedBrowserTestingModule, and both symbols exist back to Angular 17, so the supported floor is unaffected. - CI Node pins raised to 24 (#77) across
ci.yml,publish.ymlanddocs.yml. Angular 22 declaresengines.node: ^22.22.3 || ^24.15.0 || >=26.0.0and the CLI enforces it at runtime, so Node 20 is a hard failure. - Documented Angular range corrected (#77): the README and the docs-site Requirements table advertised
>=16.0.0whilepackage.jsonhas enforced>=17.0.0since 3.0.0. Both now state the enforced range. This is a documentation fix — the installable range is unchanged at the low end. - Build and test targets moved to
@angular/build(#79):angular.jsonnow uses@angular/build:ng-packagrand@angular/build:karmainstead of the@angular-devkit/build-angularequivalents, which Angular 22 deprecates as part of its Webpack support deprecation and which printed a notice on every build.@angular/buildexposes noplugins/karmaentry point, sokarma.conf.jsdrops the@angular-devkit/build-angularframework entry and its pluginrequire;@angular-devkit/build-angularis removed fromdevDependenciesentirely in favour of@angular/build.istanbul-lib-instrumentis added todevDependencies:@angular/builddeclares it as an optional peer rather than bundling it the way the devkit did, so--code-coveragefails without it. Build-tooling only — no effect on the published package. Verified with the devkit fully uninstalled and against a prunednpm citree: build exit 0 with no deprecation notice, 985 specs passing, and coverage still written tocoverage/ng-qubee/lcov.info(the pathci.ymlhands to codecov) at 95.47% statements. SECURITY.mdlists 3.x as supported (#79): the Supported Versions table stopped at2.x, leaving the currently-shipping major absent while the previous one read as newest.2.xremains supported — the roadmap sets the window at six months past the 3.0.0 release (2026-04-12).
rxjspeer range corrected to^7.2.0(#77), from^6.5.0 || ^7.0.0— a range the library could never honour.NgQubeeServiceimportsfilterfrom therxjsroot, which only became a root export in 7.2.0:require('rxjs').filterisundefinedon 6.5.0, 6.6.7, 7.0.0 and 7.1.0, and the import survives into the shipped FESM, sogenerateUri()could not work on those versions.generateUri()additionally usesthrowError(() => error), whose factory form only exists from rxjs 7 — on rxjs 6 the subscriber receives the closure instead of theError. No working consumer is affected: Angular has peeredrxjs: ^6.5.3 || ^7.4.0since v16, so every Angular application on the 7 line already resolves above this floor. The correction moves the failure from a runtimeundefinedto a clear install-time message.
[3.7.0] - 2026-06-12
Section titled “[3.7.0] - 2026-06-12”-
WordPress REST driver (
DriverEnum.WORDPRESS) (#76): new driver targeting the WordPress REST API — the JSON API shipped with every WordPress install since 4.7.WordpressRequestStrategy: bare collection-param filters (status=publish; multi-value folds to the CSV list conventioncategories=2,3); single-sortorderby=field&order=asc|desc(WordPress core accepts oneorderby, so only the first sort rule is emitted);_fields=id,titleCSV projection;_embed=author,wp:termCSV embedding;search=termglobal search; page-based pagination aspage=N&per_page=M. The wire keys are fixed by the server and ignoreIConfig.requestoverrides. The server-sideper_pagecap of 100 is left to the server (endpoint policy, not syntax).WordpressResponseStrategy: parses the bare-array body + header shape —X-WP-Total → total,X-WP-TotalPages → lastPage, and the RFC 5988Linkheader’srel="next"/rel="prev"URLs surface asnextPageUrl/prevPageUrl.currentPagederives from theprevlink’spageparam + 1 (falling back tonext− 1, then 1);perPagefrom the item count of any page with anextsuccessor;from/tofrom page × size on full pages or counting back from the total on the last page. Headers are optional at the call site (same shape as PostgREST); omission yieldsundefinedbounds. Second header-driven driver after PostgREST — uses the plainResponseOptions(onlydatais consulted, and only for envelope-wrapped bodies).- WordPress driver supports
addFilter/deleteFilters,addSort/deleteSorts,addSelect/deleteSelect,addIncludes/deleteIncludes,setSearch/deleteSearch,setLimit/setPage+ their delete counterparts.addFilterOperator(no generic operator syntax — date helpers likebefore/afterpass throughaddFilter),addFields, andaddEmbeddedthrow the matchingUnsupported*Error.
-
Payload driver (
DriverEnum.PAYLOAD) (#75): new driver targeting Payload — the TypeScript-native headless CMS / application framework; the response envelope ismongoose-paginate-v2, so the driver also fits any Express/Mongoose backend using that plugin.PayloadRequestStrategy: bracket-syntax filters merged into a singlewhere[...]block — simple single-value filters fold towhere[field][equals]=value, multi-value towhere[field][in]=v1,v2(CSV); simple and operator filters on the same field merge under onewhere[field]map;sort=-createdAt,titleCSV sorts;select[col]=trueprojection flags fromaddSelect(dotted names pass through); page-based pagination aspage=N&limit=M. The five wire keys are fixed by the REST endpoints and ignoreIConfig.requestoverrides.PayloadResponseStrategy: parses the flat{ docs, totalDocs, limit, totalPages, page, pagingCounter, prevPage, nextPage }envelope via the shared dot-path traversal.pagingCounter(the 1-indexed offset of the first doc on the page) maps straight ontofrom;tois computed frompage×limit(clamped to the total).prevPage/nextPageare page numbers, not URLs, so URL slots stayundefined.- Payload driver supports
addFilter/deleteFilters,addFilterOperator/deleteOperatorFilters,addSort/deleteSorts,addSelect/deleteSelect,setLimit/setPage+ their delete counterparts.addFields,addIncludes(population is depth-based viadepth=N, not a named-relation list),addEmbedded, andsetSearchthrow the matchingUnsupported*Error.
-
Payload
FilterOperatorEnummapping (#75):EQ→equals,GT/GTE/LT/LTE→greater_than/greater_than_equal/less_than/less_than_equal,CONTAINS→contains,ILIKE→like,IN→in(CSV),BTW→greater_than_equal+less_than_equalpair (arity-checked),NOT→not_equals(single) /not_in(multi, CSV),NULL→existswith inverted boolean (true→exists=false⇔ IS NULL; arity/type-checked).SW(no starts-with operator) and the PostgREST-onlyFTS/PHFTS/PLFTS/WFTSthrowUnsupportedFilterOperatorError. -
PayloadResponseOptions(new class) (#75): pre-configured key mapping for themongoose-paginate-v2envelope (data → 'docs',total → 'totalDocs',perPage → 'limit',lastPage → 'totalPages',currentPage → 'page',from → 'pagingCounter'); derived/URL fields default to empty paths; all keys overridable viaIPaginationConfig. -
PocketBase driver (
DriverEnum.POCKETBASE) (#74): new driver targeting PocketBase — the open-source single-binary backend (SQLite + realtime + auth).PocketbaseRequestStrategy: a singlefilter=(...)expression-language parameter holding&&-joined terms — simple single-value filters fold tofield='value'(strings quoted with backslash-escaped embedded quotes, numbers/booleans bare), multi-value filters fold to an OR group ((field='v1' || field='v2'));sort=-created,titleCSV sorts;fields=id,titleCSV projection fromaddSelect;expand=author,commentsCSV relation expansion fromaddIncludes(dotted names pass through); page-based pagination aspage=N&perPage=M. The six wire keys are fixed by the records API and ignoreIConfig.requestoverrides.PocketbaseResponseStrategy: parses the flat{ page, perPage, totalItems, totalPages, items }records-list envelope via the shared dot-path traversal;from/toare computed frompage×perPage(clamped to the total), URL slots stayundefined(no navigation links in the envelope).- PocketBase driver supports
addFilter/deleteFilters,addFilterOperator/deleteOperatorFilters,addSort/deleteSorts,addSelect/deleteSelect,addIncludes/deleteIncludes,setLimit/setPage+ their delete counterparts.addFields,addEmbedded, andsetSearchthrow the matchingUnsupported*Error(no per-model fields, no embedded projection, no global search param — use aCONTAINSoperator filter instead).
-
PocketBase
FilterOperatorEnummapping (#74):EQ→field='v',GT/GTE/LT/LTE→>/>=/</<=,CONTAINS/ILIKE→field~'v'(PocketBase’s~is case-insensitive and auto-wraps in%...%),SW→field~'v%'(explicit wildcard),IN→OR group,BTW→AND group(field>=min && field<=max)(arity-checked),NOT→!=(single) / AND-ed!=group (multi),NULL→field=null/field!=null(boolean dispatch, arity/type-checked). PostgREST-onlyFTS/PHFTS/PLFTS/WFTSthrowUnsupportedFilterOperatorError. -
PocketbaseResponseOptions(new class) (#74): pre-configured key mapping for the records-list envelope (data → 'items',total → 'totalItems',lastPage → 'totalPages',currentPage → 'page',perPage → 'perPage'); derived/URL fields default to empty paths; all keys overridable viaIPaginationConfig. -
Feathers driver (
DriverEnum.FEATHERS) (#53): new driver targeting FeathersJS — the realtime/REST application framework for Node.js, covering the query syntax shared by all Feathers database adapters.FeathersRequestStrategy: bare exact-match simple filters (field=value; multi-value folds tofield[$in][0]=v1&field[$in][1]=v2),field[$op]=valueoperator filters,$sort[field]=1|-1multi-field sort map,$select[N]=colindexed projection, offset pagination as$limit=N&$skip=Mwithskipderived from the 1-indexed page ((page − 1) × limit). The dollar-prefixed system keys are fixed by the adapter-commons parser and ignoreIConfig.requestoverrides.FeathersResponseStrategy: parses the{ total, limit, skip, data }envelope — offset-based, with no page number and no navigation URLs.currentPagederives as⌊skip ÷ limit⌋ + 1(zero/missinglimit, e.g. a$limit=0count-only query, falls back to page 1),lastPageasceil(total ÷ limit),from/toas 1-indexed offsets fromskipand the on-page item count. URL slots stayundefined.- Feathers driver supports
addFilter/deleteFilters,addFilterOperator/deleteOperatorFilters,addSort/deleteSorts,addSelect/deleteSelect,setLimit/setPage+ their delete counterparts.addFields,addIncludes,addEmbedded, andsetSearchthrow the matchingUnsupported*Error(no per-model fields, relation loading is resolver-side, no global search param).
-
Feathers
FilterOperatorEnummapping (#53):EQ→barefield=value,GT/GTE/LT/LTE→$gt/$gte/$lt/$lte,IN→$in(indexed array),BTW→$gte+$ltepair on the same field (arity-checked),NOT→$ne(single) /$nin(multi, indexed array).CONTAINS/ILIKE/SW(LIKE-style matching is adapter-specific, not part of the common syntax),NULL(no null-check operator), and the PostgREST-onlyFTS/PHFTS/PLFTS/WFTSthrowUnsupportedFilterOperatorError. -
FeathersResponseOptions(new class) (#53): pre-configured key mapping for the Feathers envelope (data → 'data',total → 'total',perPage → 'limit'); derived/URL fields default to empty paths; all keys overridable viaIPaginationConfig(the envelope’sskipkey is fixed and read directly by the strategy). -
PostgREST embedded resources (#66): new fluent methods
addEmbedded(relation, ...columns)/deleteEmbedded(...relations)onNgQubeeService, exposing PostgREST’s idiomatic join mechanism (select=col,rel(col1,col2)).- Embedded fragments are spliced into the single
select=query parameter alongside flat columns fromaddSelect— never emitted as a secondselect. With embedded relations but no flat select, the flat part defaults to*so the base row’s columns stay in the projection (select=*,author(*)). addEmbedded('author')with no columns emitsauthor(*); repeated calls for the same relation merge-dedup the columns;deleteEmbeddedremoves the whole relation entry;reset()clears the map. Neither method resets the page (column shape change, not record-set change).IQueryBuilderStategains anembeddedfield (new publicEmbeddedtype,{ [relation: string]: string[] }), andIStrategyCapabilitiesgains anembeddedflag —trueonly on the PostgREST strategy. All other drivers throw the new publicUnsupportedEmbeddedError.- Out of scope (per issue): nested embedding (
author(posts(*))), filtering on embedded columns, ordering by embedded columns.
- Embedded fragments are spliced into the single
-
OData driver (
DriverEnum.ODATA) (#49): new driver targeting OData v4 — the OASIS-standard query protocol of the Microsoft/.NET ecosystem (ASP.NET Core OData, SAP, Microsoft Graph).OdataRequestStrategy: single$filter=parameter holdingand-joined expression-language terms — simple single-value filters fold toeq(status eq 'active'), multi-value filters fold to the v4.01inlist operator (status in ('active','pending'));$orderby=field asc,other descCSV sorts;$select=col1,col2projection;$expand=combining bare relations fromaddIncludesand inline-projected ones fromaddEmbedded(rel($select=col1,col2));$search=termfree-text search; offset pagination as$top=N&$skip=M(skip derived from the 1-indexed page, omitted on page 1) plus a constant$count=trueso responses carry the total. The$-prefixed system query option names are fixed by the spec and ignoreIConfig.requestoverrides. String literals are single-quoted with embedded quotes doubled (name eq 'O''Brien'); numbers and booleans emit bare.OdataResponseStrategy: parses the{ "@odata.count", "@odata.nextLink", "value" }envelope. The body names no current page or page size, so both are derived from the@odata.nextLinkURL:perPagefrom its$top(falling back to the on-page item count),currentPageas$skip ÷ perPage,lastPageasceil(total ÷ perPage). Without a usable link (last page, or$skiptokenserver-driven paging) the page falls back to 1 — guaranteed correct only for single-page results. RelativenextLinkvalues are handled. Envelope keys contain literal dots, so they are read with flat bracket access (no dot-path traversal).- OData driver supports
addFilter/deleteFilters,addFilterOperator/deleteOperatorFilters,addSort/deleteSorts,addSelect/deleteSelect,addIncludes/deleteIncludes,addEmbedded/deleteEmbedded,setSearch/deleteSearch,setLimit/setPage+ their delete counterparts — the broadest capability set so far. OnlyaddFieldsthrows (UnsupportedFieldSelectionError: no per-model field selection in OData).
-
OData
FilterOperatorEnummapping (#49):EQ→eq,GT/GTE/LT/LTE→gt/ge/lt/le,CONTAINS→contains(field,'val'),ILIKE→contains(tolower(field),tolower('val')),SW→startswith(field,'val'),IN→field in (…),BTW→two AND-ed termsfield ge min and field le max(arity-checked),NOT→oneneterm per value (AND-ed),NULL→eq null/ne null(boolean dispatch). PostgREST-onlyFTS/PHFTS/PLFTS/WFTSthrowUnsupportedFilterOperatorError. -
OdataResponseOptions(new class) (#49): pre-configured key mapping for the OData envelope (data → 'value',total → '@odata.count',nextPageUrl → '@odata.nextLink'); derived fields default to empty paths; all keys overridable viaIPaginationConfig. -
Directus driver (
DriverEnum.DIRECTUS) (#52): new driver targeting Directus — the open-source headless CMS / data platform with underscore-prefixed operators inside a nested bracket filter syntax.DirectusRequestStrategy: nested bracket filters — simple single-value filters fold tofilter[field][_eq]=value, multi-value tofilter[field][_in]=v1,v2(CSV); simple and operator filters on the same field merge under onefilter[field]block;sort=-created_at,nameCSV sorts; a singlefields=CSV combining flat columns fromaddSelect, whole relations fromaddIncludes(rel.*), and column-projected relations fromaddEmbedded(rel.col— with relations but no flat select the flat part defaults to*);search=termglobal search; page-based pagination (limit=N&page=N, 1-indexed) plus a constantmeta=total_count,filter_countso responses carry totals. Thefilter/sort/fields/search/limit/pagekeys honourIConfig.requestoverrides;metais fixed. Directus’deep[...]query options and nested relation filtering are out of scope.DirectusResponseStrategy: parses the{ data, meta: { total_count, filter_count } }envelope.totaldefaults tometa.filter_count(the count matching the current filter — the relevant total for paging; point the path atmeta.total_countfor the unfiltered size). The envelope names no current page or page size, sopagefalls back to 1 (guaranteed correct only for single-page results) andlastPage/from/toresolve only when the response provably holds the whole filtered set — or when paths are pointed at a custom wrapper that does include paging fields (dot notation supported).- Directus driver supports
addFilter/deleteFilters,addFilterOperator/deleteOperatorFilters,addSort/deleteSorts,addSelect/deleteSelect,addIncludes/deleteIncludes,addEmbedded/deleteEmbedded,setSearch/deleteSearch,setLimit/setPage+ their delete counterparts. OnlyaddFieldsthrows (UnsupportedFieldSelectionError: relational projection uses dot paths, not afields[model]map).
-
Directus
FilterOperatorEnummapping (#52):EQ/GT/GTE/LT/LTE→_eq/_gt/_gte/_lt/_lte,CONTAINS→_contains,ILIKE→_icontains,SW→_starts_with,IN→_in(CSV),BTW→_betweenwithmin,max(arity-checked),NOT→_neq(single) /_nin(multi, CSV),NULL→_null=true/_nnull=true(boolean dispatch). PostgREST-onlyFTS/PHFTS/PLFTS/WFTSthrowUnsupportedFilterOperatorError. -
DirectusResponseOptions(new class) (#52): pre-configured key mapping for the Directus envelope (data → 'data',total → 'meta.filter_count'); paging fields default to empty paths; all keys overridable viaIPaginationConfig. -
json-server driver (
DriverEnum.JSON_SERVER) (#55): new driver targeting json-server — the de-facto standard mock REST API for frontend prototyping.JsonServerRequestStrategy: bare exact-match simple filters (field=value; multi-value folds tofield:in=v1,v2), colon-syntax operator filters (field:op=value),_sort=-views,titleCSV sorts,q=termfull-text search, pagination as_page=N&_per_page=M. The underscore-prefixed system keys andqare fixed by the server and ignoreIConfig.requestoverrides.JsonServerResponseStrategy: parses the v1 flat envelope{ first, prev, next, last, pages, items, data }where the navigation fields are page numbers, not URLs —currentPagederives asprev + 1(null → 1),perPagefrom the item count of any page with anextsuccessor,from/tofrom page × size on full pages or counting back from the total on the last page (from = items - data.length + 1,to = items). URL slots stayundefined.- json-server driver supports
addFilter/deleteFilters,addFilterOperator/deleteOperatorFilters,addSort/deleteSorts,setSearch/deleteSearch,setLimit/setPage+ their delete counterparts.addSelect,addFields,addIncludes, andaddEmbeddedthrow the matchingUnsupported*Error(no projection or relation-loading parameters exposed).
-
json-server
FilterOperatorEnummapping (#55):EQ→:eq,GT/GTE/LT/LTE→:gt/:gte/:lt/:lte,CONTAINS→:contains,SW→:startsWith,IN→:in(CSV),BTW→two AND-ed paramsfield:gte=min&field:lte=max(arity-checked),NOT→one:neparam per value (AND-ed).ILIKE(no case-insensitive variant),NULL(no null-check operator), and the PostgREST-onlyFTS/PHFTS/PLFTS/WFTSthrowUnsupportedFilterOperatorError. -
JsonServerResponseOptions(new class) (#55): pre-configured key mapping for the v1 envelope (data → 'data',total → 'items',lastPage → 'pages'); derived/URL fields default to empty paths; all keys overridable viaIPaginationConfig. -
API Platform driver (
DriverEnum.API_PLATFORM) (#54): new driver targeting API Platform — the leading REST/GraphQL framework for PHP/Symfony.ApiPlatformRequestStrategy: bare exact-match simple filters (field=value; multi-value uses the array syntaxfield[]=v1&field[]=v2with OR semantics; relation dot paths likeauthor.name=Johnpass through), bracket-syntax operator filters (RangeFilter, SearchFilter strategies, ExistsFilter), oneorder[field]=asc|descparam per sort rule, pagination aspage=N&itemsPerPage=M.pagehonoursIConfig.requestoverrides;orderanditemsPerPageare fixed conventions. DateFilter has no enum counterpart but passes throughaddFilter('createdAt[after]', '2023-01-01').ApiPlatformResponseStrategy: parses the default Hydra/JSON-LD envelope (hydra:member,hydra:totalItems,hydra:view). The body names no current page or page size, so both derive from thehydra:viewURLs:currentPagefrom the view@id’spageparam,perPagefrom itsitemsPerPageparam (echoed because the request strategy always sends it; falls back to the item count of a page with ahydra:nextsuccessor),lastPagefromhydra:last’spageparam orceil(total ÷ perPage). Relative Hydra links are handled and surfaced as-is. JSON:API / HAL serialization formats are out of scope (use the JSON:API driver).- API Platform driver supports
addFilter/deleteFilters,addFilterOperator/deleteOperatorFilters,addSort/deleteSorts,setLimit/setPage+ their delete counterparts.addSelect,addFields,addIncludes,addEmbedded, andsetSearchthrow the matchingUnsupported*Error(projection and embedding are server-side serialization-group concerns; no standard global search param).
-
API Platform
FilterOperatorEnummapping (#54):EQ→bare exact match,GT/GTE/LT/LTE→RangeFilterfield[gt]=/[gte]/[lt]/[lte],BTW→field[between]=min..max(arity-checked),CONTAINS→field[partial]=,ILIKE→field[ipartial]=,SW→field[start]=,IN→array syntax,NULL→ExistsFilter with inverted boolean (true→field[exists]=false⇔ IS NULL; arity/type-checked).NOT(no negation filter in core) and the PostgREST-onlyFTS/PHFTS/PLFTS/WFTSthrowUnsupportedFilterOperatorError. -
ApiPlatformResponseOptions(new class) (#54): pre-configured dot-path mapping for the Hydra envelope (data → 'hydra:member',total → 'hydra:totalItems', navigation URLs underhydra:view.*,path → 'hydra:view.@id'for page/size derivation); all keys overridable viaIPaginationConfig.
- Options classes exported from the public API (#73):
QueryBuilderOptions,ResponseOptions, and the driver-specificResponseOptionssubclasses (DrfResponseOptions,JsonApiResponseOptions,NestjsResponseOptions,NestjsxCrudResponseOptions,SieveResponseOptions,SpringResponseOptions,StrapiResponseOptions) were documented as public but never exported frompublic-api.ts. The exportedNG_QUBEE_REQUEST_OPTIONS/NG_QUBEE_RESPONSE_OPTIONStokens can now be typed by consumers without deep imports.
[3.6.0] - 2026-06-10
Section titled “[3.6.0] - 2026-06-10”-
@nestjsx/crud driver (
DriverEnum.NESTJSX_CRUD) (#44): new driver targeting @nestjsx/crud for NestJS.NestjsxCrudRequestStrategy: pipe-delimited repeatable filters (filter=field||$eq||valuesingle,filter=field||$in||v1,v2multi-value), repeatablesort=field,ASCsorts (uppercase direction), flatfields=col1,col2selection, repeatablejoin=relationfor related resources, page-based pagination (page=N&limit=N).NestjsxCrudResponseStrategy: parses the flat{ data, count, total, page, pageCount }getMany envelope; computesfrom/tofrom page × count.countis the on-page entity count, so derived indices can underestimate on a partial last page (documented).- @nestjsx/crud driver supports
addFilter/deleteFilters,addFilterOperator/deleteOperatorFilters,addSort/deleteSorts,addSelect/deleteSelect(mapped tofields),addIncludes/deleteIncludes(mapped tojoin),setLimit/setPage+ their delete counterparts.addFieldsandsetSearchthrow the matchingUnsupported*Error(no per-model field selection; thes={...}search parameter is JSON-shaped, not a plain term).
-
@nestjsx/crud
FilterOperatorEnummapping (#44):EQ/GT/GTE/LT/LTE/INmap identically;CONTAINS→$cont,ILIKE→$contL,SW→$starts,BTW→$between(arity-checked, emitsmin,max),NOT→$ne(single) /$notin(multi),NULL→$isnull/$notnull(boolean dispatch, no value segment). PostgREST-onlyFTS/PHFTS/PLFTS/WFTSthrowUnsupportedFilterOperatorError. -
NestjsxCrudResponseOptions(new class) (#44): pre-configured key mapping for the getMany envelope (currentPage → 'page',lastPage → 'pageCount',perPage → 'count',total → 'total'); all paths overridable viaIPaginationConfig. -
Spring Data REST driver (
DriverEnum.SPRING) (#45): new driver targeting Spring Data REST — the standard pagination/sorting convention in the Java/Spring Boot ecosystem.SpringRequestStrategy: repeatablesort=field,ascparams (one occurrence per rule, lowercase direction), pagination aspage=N&size=Nwith 0-indexedpageon the wire — library state stays 1-indexed and the strategy subtracts 1 at emission time.SpringResponseStrategy: parses the HAL envelope —page.{size,totalElements,totalPages,number}(converting the 0-indexednumberback to a 1-indexed page), navigation URLs from_links.*.href, and the data array from_embedded. The collection key under_embeddedis the resource rel name and cannot be known statically, so the strategy picks the first array inside_embeddedby default; pin an exact path viaIConfig.response(data: '_embedded.users'). Missing_embedded(empty result set) yields an empty array.- First sort-only driver:
addSort/deleteSortsandsetLimit/setPageare supported;addFilter,addFilterOperator,addSelect,addFields,addIncludes, andsetSearchall throw the matchingUnsupported*Error(Spring Data REST defines no standard wire convention for these — they are custom query methods / Specifications server-side).
-
SpringResponseOptions(new class) (#45): pre-configured dot-path mapping for the HAL envelope (currentPage → 'page.number',total → 'page.totalElements',lastPage → 'page.totalPages',perPage → 'page.size',data → '_embedded', links →_links.*.href); all paths overridable viaIPaginationConfig. -
Sieve driver (
DriverEnum.SIEVE) (#46): new driver targeting Sieve for ASP.NET Core.SieveRequestStrategy: singlefilters=parameter holding comma-joined (AND)Field{op}Valueexpression terms — simple single-value filters fold to==, multi-value filters fold to a value-level pipe OR (status==active|pending);sorts=field,-otherCSV with-prefix for DESC; page-based pagination (page=N&pageSize=N).SieveResponseStrategy: Sieve defines no response envelope (it returns anIQueryablethe developer wraps), so the strategy ships sensible defaults for the common hand-rolledPagedResult<T>shape{ data, page, pageSize, total, totalPages }, with every key path configurable viaIConfig.response(dot notation supported).- Sieve driver supports
addFilter/deleteFilters,addFilterOperator/deleteOperatorFilters,addSort/deleteSorts,setLimit/setPage+ their delete counterparts.addFields,addIncludes,addSelect, andsetSearchthrow the matchingUnsupported*Error(Sieve has no projection, relation-loading, or global-search parameters; useCONTAINS/ILIKEoperator filters for partial matches).
-
Sieve
FilterOperatorEnummapping (#46):EQ→==,GT/GTE/LT/LTE→>/>=/</<=,CONTAINS→@=,ILIKE→@=*,SW→_=,IN→==with value-level pipe OR,BTW→two AND-ed termsfield>=min,field<=max(arity-checked),NOT→one!=term per value (AND-ed),NULL→==null/!=null(boolean dispatch). PostgREST-onlyFTS/PHFTS/PLFTS/WFTSthrowUnsupportedFilterOperatorError. -
SieveResponseOptions(new class) (#46): pre-configured key mapping for the defaultPagedResult<T>shape (currentPage → 'page',perPage → 'pageSize',lastPage → 'totalPages',total → 'total'); all paths overridable viaIPaginationConfig.
Changed
Section titled “Changed”package.jsonkeywordsexpanded withcrud,nestjsx-crud(#44),hal,spring,spring-data-rest(#45), andaspnetcore,sieve(#46) to surface the new drivers on npm. List remains alphabetised.
- DRF strategies exported from the public API (#72):
DrfRequestStrategyandDrfResponseStrategywere missing frompublic-api.ts— every other driver’s strategy pair was exported. Consumers can now import both directly fromng-qubee.
[3.5.0] - 2026-05-15
Section titled “[3.5.0] - 2026-05-15”- Django REST Framework driver (
DriverEnum.DRF) (#47): new driver targeting Django REST Framework with django-filter.DrfRequestStrategy: flat key=value filters with django-filter’s double-underscore lookup convention (field__gte=N,field__icontains=value,field__in=v1,v2,v3),ordering=-field1,field2for sorts (-prefix = DESC),search=term(DRF’sSearchFilter), page-based pagination (page=N&page_size=M). Multi-value simple filters collapse tofield__in=....DrfResponseStrategy: parses the{ count, next, previous, results }envelope and derivescurrentPage/perPage/lastPageby parsing thenextandpreviousURLs — DRF’sPageNumberPaginationdoesn’t echo the current page back in the body, so the strategy reads?page=Nand?page_size=Nfrom the link URLs.previous=null→ page 1;previouswithout?page→ page 2 (DRF omitspage=1);previouswith?page=N→ pageN+1.- DRF driver supports
addFilter/deleteFilters,addFilterOperator/deleteOperatorFilters,addSort/deleteSorts,setSearch/deleteSearch,setLimit/setPage+ their delete counterparts.addFields,addIncludes, andaddSelectthrow the matchingUnsupported*Error(DRF has no first-class per-model fields, no relation includes, and no flat column-list select —django-restqladds these but is not core DRF).
- DRF
FilterOperatorEnummapping (#47):EQemits no suffix (django-filter’s defaultexact);GT/GTE/LT/LTE/CONTAINSmap to identically-named lookups;ILIKE→__icontains,SW→__startswith,IN→__in(comma-joined),BTW→__range=min,max(arity-checked, requires exactly 2 values),NULL→__isnull=true|false(boolean dispatch). The genericNOTand PostgREST-onlyFTS/PHFTS/PLFTS/WFTShave no django-filter equivalent and throwUnsupportedFilterOperatorError. DrfResponseOptions(new public class) (#47): pre-configured key mapping for the DRF response envelope (data → 'results',total → 'count',nextPageUrl → 'next',prevPageUrl → 'previous').currentPage/perPage/lastPage/from/todefault to empty paths because the strategy derives them from URL inspection rather than body fields; consumers can still override any path viaIPaginationConfig.
Changed
Section titled “Changed”package.jsonkeywordsexpanded withdjangoanddrfto surface the new driver on npm. List remains alphabetised.
Internal
Section titled “Internal”AbstractFlatResponseStrategy(#71) absorbs the byte-identicalpaginate()body thatLaravelResponseStrategyandSpatieResponseStrategywere duplicating. Both response strategies collapse to one-line empty extensions, mirroring the pattern established byAbstractDotPathResponseStrategyfor JSON:API / NestJS / Strapi in #67. Empty extensions are retained so the DI tokens resolve to distinct identities and future per-driver overrides have a place to land. Zero behavior change; existing specs unchanged.
[3.4.0] - 2026-04-30
Section titled “[3.4.0] - 2026-04-30”- Strapi driver (
DriverEnum.STRAPI) (#51): new driver targeting Strapi v4/v5 headless CMS.StrapiRequestStrategy: deep-bracket filters (filters[field][$eq]=valuesingle,filters[field][$in][N]=valuemulti-value), array-style sorts (sort[N]=field:dir), flat field selection (fields[N]=col),populate[N]=relationfor related resources, page-based pagination (pagination[page]=N&pagination[pageSize]=N)StrapiResponseStrategy: parses themeta.pagination.{page,pageSize,pageCount,total}envelope, computesfrom/tofrom page × pageSize × total- Strapi driver supports
addFilter/deleteFilters,addFilterOperator/deleteOperatorFilters,addSort/deleteSorts,addSelect/deleteSelect,addIncludes/deleteIncludes(mapped topopulate),setLimit/setPage+ their delete counterparts.addFieldsandsetSearchthrow the matchingUnsupported*Error(Strapi has no per-model fields and no global search; use$contains/$containsioperator filters for partial matches)
- Strapi
FilterOperatorEnummapping (#51):EQ/GT/GTE/LT/LTE/CONTAINS/INmap directly;ILIKE→$containsi,SW→$startsWith,BTW→$between(arity-checked, expands to a 2-element array),NOT→$ne(single) /$notIn(multi),NULL→$null=true/$notNull=true(boolean dispatch). PostgREST-onlyFTS/PHFTS/PLFTS/WFTSthrowUnsupportedFilterOperatorError. StrapiResponseOptions(new public class) (#51): pre-configured dot-path mapping for the Strapi response envelope; consumers can override individual paths viaIPaginationConfig.
Changed
Section titled “Changed”- README condensed: per-driver tutorials, the full Query Builder API reference, pagination guides, and TypeScript usage examples have moved to the documentation site at ng-qubee.andreatantimonaco.me. The README now keeps a quick install + driver matrix + minimal example and points readers at the docs site for everything else.
package.jsonhomepagenow points to ng-qubee.andreatantimonaco.me (wasgithub.com/.../#readme).package.jsonkeywordsalphabetised and expanded withheadless-cms,json-api,laravel,nestjs,nestjs-paginate,spatie,strapito improve npm discoverability across the supported drivers.
[3.3.0] - 2026-04-25
Section titled “[3.3.0] - 2026-04-25”- PostgREST / Supabase driver (
DriverEnum.POSTGREST) (#50): new driver targeting PostgREST and Supabase.PostgrestRequestStrategy: operator-prefixed filters (col=eq.valsingle,col=in.(v1,v2,v3)multi-value),order=col.asc,col.descsorting, flatselect=col1,col2, offset-based pagination (limit=N&offset=Mwith offset derived frompage)PostgrestResponseStrategy: parses the total count from the HTTPContent-Rangeresponse header (opt-in viaPrefer: count=exact), derivespage/perPage/lastPage/totalwith 0-to-1-indexed conversion, tolerates missing/malformed headers gracefully- PostgREST driver supports
addFilter/deleteFilters,addFilterOperator/deleteOperatorFilters,addSort/deleteSorts,addSelect/deleteSelect,setLimit/setPage+ their delete counterparts.addFields,addIncludes, andsetSearchthrow the matchingUnsupported*Error(embedded-resource support tracked as #66)
- Full
FilterOperatorEnummapping on PostgREST (#50): every existing operator (EQ,GT,GTE,LT,LTE,ILIKE,IN,NOT,NULL,BTW,SW,CONTAINS) is translated to PostgREST’s prefix syntax, with correct semantics for the awkward ones:NOTdispatches tonot.eq.val(single) ornot.in.(v1,v2)(multi) by arityNULLmaps a boolean value tois.null/is.not.nullBTWexpands to two query params (col=gte.min&col=lte.max)SWemitslike.val*;CONTAINSemitsilike.%val%
- PostgREST full-text search operators (#50): four new
FilterOperatorEnumentries —FTS,PLFTS,PHFTS,WFTS— map to PostgREST’sto_tsquery,plainto_tsquery,phraseto_tsquery, andwebsearch_to_tsqueryrespectively. Column-scoped; use viaaddFilterOperator(column, FilterOperatorEnum.FTS, term). Language modifiers (e.g.fts(english)) are not supported in this release. InvalidFilterOperatorValueError(new public error) (#50): thrown by the PostgREST strategy when an operator’s value arity or type is unambiguously wrong at call time.BTWrequires exactly 2 values;NULLrequires exactly 1 boolean. Other operators leave validation to the server.PaginationModeEnum(new enum export) (#50):QUERY(default) orRANGE. Set viaIConfig.pagination; currently honoured only by the PostgREST driver.- RANGE-header pagination on PostgREST (#50): when
IConfig.paginationisPaginationModeEnum.RANGE,generateUri()omitslimit/offsetfrom the URL andNgQubeeService.paginationHeaders()returns{ 'Range-Unit': 'items', 'Range': 'from-to' }for the consumer to merge into the HTTP request. Other drivers leave the setting as a no-op. NgQubeeService.paginationHeaders()(new method) (#50): thin passthrough to the active strategy’s optionalbuildPaginationHeaders. Returnsnullfor drivers that don’t use header-based pagination.IRequestStrategy.buildPaginationHeaders?(new optional interface method) (#50): drivers that ship pagination metadata via HTTP headers implement it; all four pre-existing strategies omit it and the type is satisfied via TypeScript’s structural typing without source changes.HeaderBagtype (new export): union of{ get(name): string | null }(AngularHttpHeaders, nativeHeaders) andRecord<string, string | null | undefined>(plain object). Plus areadHeaderhelper that normalises access across both shapes.
Changed
Section titled “Changed”PaginationService.paginate()andIResponseStrategy.paginate()accept an optional trailingheaders?: HeaderBagparameter (#50). Backward-compatible: existing callers ignoring the new param keep working unchanged, and the four existing driver strategies (Laravel / Spatie / NestJS / JSON:API) satisfy the extended interface via TypeScript’s structural typing without any source changes. PostgREST uses it to readContent-Range; body-only drivers ignore it.
Documentation
Section titled “Documentation”- Documentation site launched (#68) at https://ng-qubee.andreatantimonaco.me. Built with Docusaurus 3, hosted on GitHub Pages with first-class versioning (3.3.0 ships as the initial snapshot;
nexttracksdevelop). Includes:- Curated guides — Getting Started, one page per driver (JSON:API / Laravel / Spatie / NestJS / PostgREST), Fetching data (with reactive
UserClientexample), Query Builder API, Pagination, Per-component instances - Auto-generated API reference via
docusaurus-plugin-typedoc, scoped tosrc/public-api.tsso it covers exactly what consumers can import - GitHub Actions deployment workflow (
.github/workflows/docs.yml) — production builds on every push tomaster, path-filtered to skip irrelevant commits
- Curated guides — Getting Started, one page per driver (JSON:API / Laravel / Spatie / NestJS / PostgREST), Fetching data (with reactive
Internal
Section titled “Internal”- Driver architecture refactor (#67) — pure internal cleanup, zero public-API change. Adding the next driver is now ~1 strategy file + 1 entry in the registry + 1 entry in
DriverEnum; nothing else needs to be touched.- Capability declarations replace driver allowlists. New
IStrategyCapabilitiesinterface andcapabilitiesgetter onIRequestStrategy; each strategy declares its supported features in one object literal.NgQubeeService._assertDriver([...], err)becomes_assertCapability(flag, err)— the service no longer readsDriverEnumfor feature gating. AbstractRequestStrategyowns theassertResourceguard, the?/&URL composition (baseUri+join), and the default positive-integervalidateLimit. Concrete strategies migrate from a mutablethis._uriaccumulator to aprotected parts(state, options): string[]template method, eliminating the copy-pasted_prepend(state)helper from all five drivers and the latent re-entrancy footgun.AbstractDotPathResponseStrategyabsorbs the byte-identical_resolve/_resolveFrom/_resolveTotraversal that JSON:API and NestJS were duplicating; both response strategies collapse to one-line empty extensions (kept for distinct DI identity).- Driver registry (
src/lib/drivers/driver-registry.ts) replaces the three parallelswitch(driver)blocks inprovide-ngqubee.ts.Record<DriverEnum, IDriverDefinition>gives compile-time exhaustiveness — adding a newDriverEnumvalue fails to compile until its definition lands in the registry.
- Capability declarations replace driver allowlists. New
[3.2.0] - 2026-04-21
Section titled “[3.2.0] - 2026-04-21”Changed
Section titled “Changed”- BEHAVIOR CHANGE — auto-reset
pageto 1 on result-set-changing mutations (#65):setLimit(),setResource(),setSearch(),deleteSearch(),addFilter(),deleteFilters(),addFilterOperator(),deleteOperatorFilters(),addSort(), anddeleteSorts()now internally resetstate.pageto1after mutating. Rationale: staying on page 5 of an old result set after a filter/sort/limit/search change is almost always a bug. Migration: if your code relied onpagepersisting across one of these mutations, callsetPage(n)explicitly afterwards. Methods that change the record shape (addFields,addIncludes,addSelect, etc.) do NOT resetpage— unchanged behavior. NgQubeeService,NestService, andPaginationServiceare now@Injectable()and constructed by Angular DI from tokens instead of a closure-captureduseFactory.provideNgQubee()andNgQubeeModule.forRoot()still accept the sameIConfigand behave identically at the root level; the refactor unlocks component-scoped instances (#64)NgQubeeServiceconstructor now takes a pre-builtQueryBuilderOptions(wasIQueryBuilderConfig);PaginationServiceconstructor now takes a pre-builtResponseOptions(wasIPaginationConfig). Both default to a fresh empty instance when omitted. Affects direct manual construction only — theprovideNgQubee()entry point is unchangedPaginationService.paginate()now auto-syncs the parsedpageandlastPageback intoNestService(#65), so pagination navigation helpers onNgQubeeServicework without consumer bookkeeping. Only positive integerlastPagevalues flip theisLastPageKnownflag; server-emitted0(empty-collection edge case) and absent fields leave the flagfalse
- Per-component service instances (#64): new
provideNgQubeeInstance()helper returns a provider array for use in a standalone component’sproviders: [...], yielding a dedicatedNgQubeeService+NestService+PaginationServicewhose state does not bleed with the app-wide instance. Driver, strategies, and options are inherited from the environment injector, so the library is still configured once at bootstrap viaprovideNgQubee()/NgQubeeModule.forRoot() - Public
InjectionTokens backing the DI wiring:NG_QUBEE_DRIVER,NG_QUBEE_REQUEST_STRATEGY,NG_QUBEE_REQUEST_OPTIONS,NG_QUBEE_RESPONSE_STRATEGY,NG_QUBEE_RESPONSE_OPTIONS - Pagination navigation helpers on
NgQubeeService(#65):nextPage(),previousPage(),firstPage(),lastPage(),goToPage(n)— fluent navigation methods that returnthis.nextPage()/previousPage()are idempotent at bounds;lastPage()andgoToPage(n)enforce bounds when known - Pagination state predicates and accessors on
NgQubeeService(#65):isFirstPage(),isLastPage(),hasNextPage(),hasPreviousPage(),currentPage(),totalPages(). Predicates are template-safe with conservative defaults when pagination bounds have not yet been synced.totalPages()throwsPaginationNotSyncedErrorbefore the firstpaginate()call - Pagination state tracking (#65):
IQueryBuilderStategainslastPage: numberandisLastPageKnown: booleanfields, populated automatically byPaginationService.paginate() PaginationNotSyncedError— new public error class thrown bylastPage()andtotalPages()when called before any paginated response has been synced into state. Exported from the library root
[3.1.0] - 2026-04-18
Section titled “[3.1.0] - 2026-04-18”- JSON:API Driver (
DriverEnum.JSON_API): New driver implementing the JSON:API specification for any compliant backend (Rails, Django, .NET, Java, Elixir, etc.)JsonApiRequestStrategy: Bracket pagination (page[number]=1&page[size]=15), bracket filters (filter[field]=value), comma-separated sorts with-prefix, per-type field selection (fields[type]=col1,col2), and includes (include=author,comments.author)JsonApiResponseStrategy: Parses nestedmetaandlinksenvelope with dot-notation path support and automaticfrom/tocomputationJsonApiResponseOptions: Pre-configured response key defaults (meta.current-page,meta.per-page,meta.total,meta.page-count,links.first,links.prev,links.next,links.last)
- JSON:API driver supports fields, filters, includes, and sorts (same feature set as Spatie)
- Fetch-all pagination for NestJS (#63):
setLimit(-1)is now accepted on the NestJS driver and honors nestjs-paginate’s convention for returning all items in a single response (server must opt-in viamaxLimit: -1)
Changed
Section titled “Changed”- Driver-scoped limit validation (#63): Limit validation moved from
NestServiceinto eachIRequestStrategyvia a newvalidateLimit(limit: number): voidcontract. The NestJS strategy accepts integer-1or>= 1; Laravel, Spatie, and JSON:API strategies continue to accept integer>= 1.NgQubeeService.setLimit()now delegates to the active strategy’s validator, so invalid values throwInvalidLimitErrorat call time InvalidLimitErrormessage now reflects which values are accepted by the active driver (adds “or -1 to fetch all items” for drivers that support the sentinel)
Internal
Section titled “Internal”- Removed dead
@Injectstring tokens and@Injectable()decorator fromNgQubeeServiceandPaginationServiceconstructors (services are instantiated viauseFactory, not Angular DI)
3.0.0 - 2026-04-12
Section titled “3.0.0 - 2026-04-12”- Three-Driver Support: Introduced a driver-based strategy pattern supporting Laravel, Spatie Query Builder, and NestJS backends
DriverEnum.LARAVEL- Pagination-only driver (limit + page)DriverEnum.SPATIE- Spatie Query Builder format with filters, sorts, fields, and includesDriverEnum.NESTJS- NestJS paginate format with operator filters, search, and flat select
- Spatie Request/Response Strategy: Full URI generation and response parsing for Spatie Query Builder format (previously named “Laravel”)
- Bracket-notation filters:
filter[field]=value - Sort format:
sort=field/sort=-field - Per-model field selection:
fields[model]=col1,col2 - Includes:
include=model1,model2
- Bracket-notation filters:
- NestJS Request Strategy: Full URI generation for nestjs-paginate format
- Dot-notation filters:
filter.field=value - Operator filters:
filter.field=$operator:valuewith 12 operators ($eq,$not,$null,$in,$gt,$gte,$lt,$lte,$btw,$ilike,$sw,$contains) - Sort format:
sortBy=field:ASC,field2:DESC - Flat field selection:
select=col1,col2 - Full-text search:
search=term
- Dot-notation filters:
- NestJS Response Strategy: Nested response parsing for nestjs-paginate format
- Parses
meta(currentPage, totalItems, itemsPerPage, totalPages) andlinks(first, previous, next, last) - Automatic
from/tocomputation from pagination metadata - Dot-notation path support for custom response key mapping
- Parses
- FilterOperatorEnum: Enum with all 12 nestjs-paginate filter operators
- IOperatorFilter Interface: Typed operator filter configuration
- IRequestStrategy / IResponseStrategy: Strategy interfaces for extensibility
- Driver Validation Errors: Seven specific error classes for driver-incompatible method calls
UnsupportedFilterError-addFilter()/deleteFilters()with LaravelUnsupportedSortError-addSort()/deleteSorts()with LaravelUnsupportedFieldSelectionError-addFields()/deleteFields()with NestJS/LaravelUnsupportedIncludesError-addIncludes()/deleteIncludes()with NestJS/LaravelUnsupportedFilterOperatorError-addFilterOperator()/deleteOperatorFilters()with Spatie/LaravelUnsupportedSelectError-addSelect()/deleteSelect()with Spatie/LaravelUnsupportedSearchError-setSearch()/deleteSearch()with Spatie/Laravel
- New Public API Methods:
setResource(name)- Set the API resource (replacessetModel())addFilterOperator(field, operator, ...values)- Add operator filter (NestJS)addSelect(...fields)- Add flat field selection (NestJS)setSearch(term)- Set search term (NestJS)deleteOperatorFilters(...fields)- Remove operator filters (NestJS)deleteSelect(...fields)- Remove select fields (NestJS)deleteSearch()- Clear search term (NestJS)
- NestService State Extensions:
operatorFilters,search,selectfields in query builder state - Test Coverage: Comprehensive tests (257 total) covering all drivers, driver validation, and strategy logic
- Documentation: Updated README with three-driver table, migration guide, and per-driver examples
Changed
Section titled “Changed”- [Breaking]
setModel()renamed tosetResource()— “model” implied ORM semantics; “resource” better reflects the API path - [Breaking]
IQueryBuilderState.modelrenamed toIQueryBuilderState.resource - [Breaking]
InvalidModelNameErrorrenamed toInvalidResourceNameError - [Breaking]
driveris now required inIConfig— no default driver - [Breaking]
NgQubeeServiceconstructor now requiresrequestStrategyanddriverparameters (injected via DI) - [Breaking]
PaginationServiceconstructor now requiresresponseStrategyparameter (injected via DI) - [Breaking]
addFilter(),deleteFilters(),addSort(),deleteSorts()now throwUnsupportedFilterError/UnsupportedSortErrorwhen used with the Laravel driver - IQueryBuilderState extended with
operatorFilters,search,selectfields - IQueryBuilderConfig extended with
search,select,sortBykeys QueryBuilderOptionsextended withsearch,select,sortBypropertiesprovideNgQubee()andNgQubeeModule.forRoot()now resolve strategies based onconfig.driver- URI generation and response parsing extracted into strategy classes
Internal
Section titled “Internal”- Refactored URI generation into
SpatieRequestStrategy,LaravelRequestStrategy, andNestjsRequestStrategy - Refactored response parsing into
SpatieResponseStrategy,LaravelResponseStrategy, andNestjsResponseStrategy - Added
NestjsResponseOptionsclass for NestJS-specific response key defaults _assertDriver()refactored to accept an array of allowed drivers
Migration Guide (2.x → 3.0)
Section titled “Migration Guide (2.x → 3.0)”- Choose a driver —
driveris now required:// Before (2.x)provideNgQubee({})// After (3.0) — use SPATIE for the same behavior as the old LARAVEL defaultprovideNgQubee({ driver: DriverEnum.SPATIE }) - Rename
setModel()→setResource():// Beforethis.ngQubee.setModel('users');// Afterthis.ngQubee.setResource('users'); - Error class rename:
InvalidModelNameError→InvalidResourceNameError - Laravel driver is now pagination-only — if you were using filters, sorts, fields, or includes with the old
DriverEnum.LARAVEL, switch toDriverEnum.SPATIE
2.1.0 - 2025-12-06
Section titled “2.1.0 - 2025-12-06”- Test Coverage: Comprehensive test suite expansion to >90% coverage
- 37 new edge case tests covering empty arrays/objects, boundary values, string edge cases, multiple operations, and deletion edge cases
- 10 new integration tests covering complete workflows, modification/deletion sequences, and signal reactivity
- Tests for complex multi-model scenarios and state immutability verification
- Previously Commented Tests: Implemented and enabled previously commented tests
- Added baseUrl functionality test to verify URL generation with base URL prepending
- Added UnselectableModelError test to verify proper error handling for invalid field selections
- CI/CD Pipeline: GitHub Actions workflow for automated testing and quality checks
- Automated linting with ESLint on every PR and push
- Automated test execution with code coverage reporting
- Automated build verification to ensure package builds successfully
- Coverage reports integration with Codecov
- Parallel job execution for faster CI feedback
- Build artifact upload for verification
- Added status badges to README (CI, Codecov, npm version, License)
- Standalone CI Support: Project can now run tests and builds independently
- Added
angular.jsonfor standalone Angular CLI configuration - Added base
tsconfig.jsonfor independent TypeScript compilation - Added all necessary devDependencies for standalone development
- Added
- Developer Experience:
- Pre-commit hook with Husky and lint-staged for automatic linting
- Dependabot configuration for automated dependency updates
- Auto-merge workflow for minor/patch dependency updates
- Documentation:
- Added CONTRIBUTING.md with development setup and PR guidelines
- Added SECURITY.md with vulnerability reporting process
- BaseUrl Support: Fixed
_prepend()method in NgQubeeService to properly prepend baseUrl when generating URIs- URIs now correctly include the base URL when set via
setBaseUrl() - Example:
setBaseUrl('https://api.example.com')now produceshttps://api.example.com/users?...
- URIs now correctly include the base URL when set via
- DeleteSorts Bug: Fixed
deleteSorts()method in NestService to correctly remove all specified sorts- Method now searches for sort indices in the working array instead of the computed signal
- Fixes issue where deleting multiple sorts would fail to remove all items
- Error Handling in generateUri(): Fixed
generateUri()method to properly handle synchronous errors as Observable errors- Wrapped
_parse()call in try-catch to convert thrown errors into Observable errors - Errors like
UnselectableModelErrorare now properly caught by Observable error handlers
- Wrapped
- CI Fixes: Multiple fixes for standalone CI execution
- Fixed lint job directory path
- Fixed tsconfig paths for standalone builds
- Fixed karma coverage output path
- Restored missing
throwErrorimport
2.0.5 - 2025-12-04
Section titled “2.0.5 - 2025-12-04”- Input Validation: Added robust input validation with custom error classes
InvalidModelNameError- Thrown when model name is empty, null, undefined, or whitespace-onlyInvalidPageNumberError- Thrown when page is not a positive integer (must be >= 1)InvalidLimitError- Thrown when limit is not a positive integer (must be >= 1)- Validation occurs in setters for
model,page, andlimitproperties
- Documentation: Comprehensive JSDoc comments added to all public methods in NestService
- Added
@exampletags demonstrating real-world usage for each method - Documented error scenarios with
@throwstags - Improved parameter descriptions with concrete examples
- Added
- Comprehensive test suite for duplicate prevention with 9 new test cases covering edge cases
- Deep cloning tests with 5 new test cases to verify immutability of state operations
- Input validation tests with 18 new test cases covering all validation scenarios
- Duplicate Prevention:
addFields(),addFilters(), andaddIncludes()now automatically prevent duplicate valuesaddFields()prevents duplicate field names for each modeladdFilters()prevents duplicate filter values for each filter keyaddIncludes()prevents duplicate include values- All methods now use Set-based deduplication for efficient duplicate removal
- Deep Cloning: Fixed shallow clone issues in
deleteFields()anddeleteFilters()methodsdeleteFields()now uses deep cloning via_clone()method to prevent state mutationsdeleteFilters()now uses deep cloning via_clone()method to prevent state mutations- Prevents accidental mutations to the original state when deleting items
Removed
Section titled “Removed”- Removed commented dead code from ng-qubee.service.ts (incomplete
_removeArgIfEmptymethod)
2.0.4 - 2025-12-03
Section titled “2.0.4 - 2025-12-03”- ESLint configuration with @angular-eslint for modern linting
- ESLint flat config format (eslint.config.mjs) for ESLint v9 compatibility
- npm scripts for linting:
npm run lintandnpm run lint:fix - Comprehensive ESLint rules for TypeScript and Angular best practices
- Template accessibility linting rules
- peerDependencies for
@angular/core(>=16.0.0 <22.0.0) andrxjs(^6.5.0 || ^7.0.0) - Repository metadata in package.json (repository, bugs, homepage)
- Keywords for better npm discoverability (angular, query-builder, api, pagination, rxjs, filter, typescript, signals)
Changed
Section titled “Changed”- [Breaking] Migrated from deprecated TSLint to ESLint
- [Breaking] Minimum Angular version is now 16.0.0 (due to Signals API usage)
- Updated
qsfrom 6.11.2 to 6.14.0 - Updated
@types/qsfrom 6.9.11 to 6.14.0 - Updated
tslibfrom 2.3.0 to 2.8.1 - Updated
karma-firefox-launcherfrom 2.1.1 to 2.1.3 - Updated TypeScript compilation target from ES2015 to ES2020 for better modern JavaScript support
- Updated TypeScript lib from ES2018 to ES2020
Removed
Section titled “Removed”- TSLint configuration (tslint.json) - replaced with ESLint
- Unused import
IQueryBuilderConfigin response-options.ts - Unused import
EnvironmentProvidersin provide-ngqubee.spec.ts - Deprecated
enableIvy: falsefrom tsconfig.lib.json (Ivy is now the default and only option) - Deprecated
skipTemplateCodegenoption from Angular compiler options - Deprecated
fullTemplateTypeCheckoption from Angular compiler options
- ESLint warnings for intentional use of
anytype in flexible API interfaces - Naming convention issues in test files for API response mocks
- Code formatting and style consistency across the codebase
Security
Section titled “Security”- Zero vulnerabilities reported by npm audit
- All dependencies updated to latest secure versions
Developer Experience
Section titled “Developer Experience”- Added ESLint disable comments for legitimate edge cases
- Improved code quality with modern linting standards
- Better TypeScript strict mode compatibility
2.0.3 - 2024-02-15
Section titled “2.0.3 - 2024-02-15”- Readme correction
- Proper dist folder name
- Wrong import statement
- npm wrong published package