@haddowg/json-api-angular 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -166,9 +166,9 @@ function jsonApiMutation(mutation, options = {}) {
166
166
  //#region src/pagination.ts
167
167
  /**
168
168
  * The `page` params for the page after `list`, read from its `next` link, or `undefined` on the
169
- * last page (or before anything has loaded). Works the same for page, offset and cursor
170
- * pagination, because the server's link already carries the right parameters. Feed the result
171
- * back into the signal your resource's params read:
169
+ * last page (or before anything has loaded). Works the same for every strategy, because the
170
+ * server's link already carries the right parameters. Feed the result back into the signal your
171
+ * resource's params read:
172
172
  *
173
173
  * ```ts
174
174
  * const next = nextPage(this.albums.value())
@@ -176,19 +176,24 @@ function jsonApiMutation(mutation, options = {}) {
176
176
  * ```
177
177
  */
178
178
  function nextPage(list) {
179
- return pageParams(list?.$page.links.next);
179
+ return pageParams(list?.$page, list?.$page.links.next);
180
180
  }
181
181
  /** The `page` params for the page before `list`, from its `prev` link. See {@link nextPage}. */
182
182
  function prevPage(list) {
183
- return pageParams(list?.$page.links.prev);
183
+ return pageParams(list?.$page, list?.$page.links.prev);
184
184
  }
185
- function pageParams(link) {
186
- if (link === void 0) return;
185
+ const CURSOR_ROLES = ["after", "before"];
186
+ function pageParams(page, link) {
187
+ if (page === void 0 || link === void 0) return;
188
+ const keys = Object.entries(page.keys);
187
189
  const query = link.includes("?") ? link.slice(link.indexOf("?") + 1).split("#")[0] : "";
188
190
  const params = {};
189
- for (const [key, value] of new URLSearchParams(query)) {
190
- const match = /^page\[([^\]]+)\]$/.exec(key);
191
- if (match?.[1] !== void 0) params[match[1]] = value;
191
+ for (const [name, value] of new URLSearchParams(query)) {
192
+ const member = /^page\[([^\]]+)\]$/.exec(name)?.[1];
193
+ if (member === void 0) continue;
194
+ const role = keys.find(([, key]) => key === member)?.[0];
195
+ const count = Number(value);
196
+ params[member] = role !== void 0 && !CURSOR_ROLES.includes(role) && value !== "" && Number.isFinite(count) ? count : value;
192
197
  }
193
198
  return params;
194
199
  }
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { EnvironmentProviders, InjectionToken, Injector, ResourceRef, ResourceStatus, Signal, ValueEqualityFn } from "@angular/core";
2
- import { ClientOptions, Collection, JsonApiError, JsonApiTransport, Page, StructuralGuardError } from "@haddowg/json-api-client";
2
+ import { ClientOptions, Collection, ErrorCodeOf, JsonApiError, JsonApiTransport, Page, PageParamsOf, StructuralGuardError } from "@haddowg/json-api-client";
3
3
  import { HttpClient } from "@angular/common/http";
4
4
 
5
5
  //#region src/provide.d.ts
@@ -59,9 +59,9 @@ declare function injectJsonApiClient<C = RegisteredClient>(token?: InjectionToke
59
59
  /**
60
60
  * What a read can fail with: a `JsonApiError` for a non-2xx response, a `StructuralGuardError`
61
61
  * for a response that isn't a JSON:API document, or any other error (a network failure, a
62
- * validator's error). Narrow with `instanceof`.
62
+ * validator's error). Narrow with `instanceof`. `E` types the `JsonApiError`'s codes.
63
63
  */
64
- type JsonApiResourceError = JsonApiError | StructuralGuardError | Error;
64
+ type JsonApiResourceError<E extends string = string> = JsonApiError<E> | StructuralGuardError | Error;
65
65
  /** What the loader receives each time the params change or the resource reloads. */
66
66
  interface JsonApiLoaderContext<C, P> {
67
67
  client: C;
@@ -93,13 +93,14 @@ interface JsonApiResourceOptions<T, P, C> {
93
93
  /**
94
94
  * Angular's `ResourceRef` with `error()` typed to what a JSON:API read throws, plus
95
95
  * `jsonApiError()`, the error when it is a `JsonApiError` (handy in templates, which can't
96
- * use `instanceof`).
96
+ * use `instanceof`). `E` is the client's error-code union, so a generated client's
97
+ * `jsonApiError()` carries its server's `ErrorCode`.
97
98
  */
98
- interface JsonApiResourceRef<T> extends Omit<ResourceRef<T>, 'error' | 'hasValue'> {
99
- readonly error: Signal<JsonApiResourceError | undefined>;
100
- readonly jsonApiError: Signal<JsonApiError | undefined>;
99
+ interface JsonApiResourceRef<T, E extends string = string> extends Omit<ResourceRef<T>, 'error' | 'hasValue'> {
100
+ readonly error: Signal<JsonApiResourceError<E> | undefined>;
101
+ readonly jsonApiError: Signal<JsonApiError<E> | undefined>;
101
102
  /** Whether a value is loaded. Narrows `value()` to exclude `undefined`, in templates too. */
102
- hasValue(this: T extends undefined ? this : never): this is JsonApiResourceRef<Exclude<T, undefined>>;
103
+ hasValue(this: T extends undefined ? this : never): this is JsonApiResourceRef<Exclude<T, undefined>, E>;
103
104
  hasValue(): boolean;
104
105
  }
105
106
  /**
@@ -117,8 +118,8 @@ interface JsonApiResourceRef<T> extends Omit<ResourceRef<T>, 'error' | 'hasValue
117
118
  */
118
119
  declare function jsonApiResource<T, P = unknown, C = RegisteredClient>(options: JsonApiResourceOptions<T, P, C> & {
119
120
  defaultValue: T;
120
- }): JsonApiResourceRef<T>;
121
- declare function jsonApiResource<T, P = unknown, C = RegisteredClient>(options: JsonApiResourceOptions<T, P, C>): JsonApiResourceRef<T | undefined>;
121
+ }): JsonApiResourceRef<T, ErrorCodeOf<C>>;
122
+ declare function jsonApiResource<T, P = unknown, C = RegisteredClient>(options: JsonApiResourceOptions<T, P, C>): JsonApiResourceRef<T | undefined, ErrorCodeOf<C>>;
122
123
  //#endregion
123
124
  //#region src/mutation.d.ts
124
125
  /** Options for {@link jsonApiMutation}. */
@@ -128,17 +129,20 @@ interface JsonApiMutationOptions<C> {
128
129
  /** Create the mutation outside an injection context. */
129
130
  injector?: Injector;
130
131
  }
131
- /** The signal-based handle {@link jsonApiMutation} returns. */
132
- interface JsonApiMutationRef<I, T> {
132
+ /**
133
+ * The signal-based handle {@link jsonApiMutation} returns. `E` is the client's error-code union,
134
+ * so a generated client's `jsonApiError()` carries its server's `ErrorCode`.
135
+ */
136
+ interface JsonApiMutationRef<I, T, E extends string = string> {
133
137
  /**
134
138
  * Run the write. Resolves with its result and rejects with its error; the signals track the
135
139
  * latest call either way.
136
140
  */
137
141
  mutate(input: I): Promise<T>;
138
142
  readonly isPending: Signal<boolean>;
139
- readonly error: Signal<JsonApiResourceError | undefined>;
143
+ readonly error: Signal<JsonApiResourceError<E> | undefined>;
140
144
  /** The error when it is a `JsonApiError`; use `byPath()` on it for form errors. */
141
- readonly jsonApiError: Signal<JsonApiError | undefined>;
145
+ readonly jsonApiError: Signal<JsonApiError<E> | undefined>;
142
146
  /** The latest successful result. */
143
147
  readonly value: Signal<T | undefined>;
144
148
  /** Clear the value and error. A call still in flight no longer updates the signals. */
@@ -155,29 +159,32 @@ interface JsonApiMutationRef<I, T> {
155
159
  * )
156
160
  * ```
157
161
  */
158
- declare function jsonApiMutation<I, T, C = RegisteredClient>(mutation: (client: C, input: I) => PromiseLike<T>, options?: JsonApiMutationOptions<C>): JsonApiMutationRef<I, T>;
162
+ declare function jsonApiMutation<I, T, C = RegisteredClient>(mutation: (client: C, input: I) => PromiseLike<T>, options?: JsonApiMutationOptions<C>): JsonApiMutationRef<I, T, ErrorCodeOf<C>>;
159
163
  //#endregion
160
164
  //#region src/pagination.d.ts
161
- /** The `page` query a link selects: its `page[...]` parameters, keyed without the prefix. */
162
- type PageParams = Record<string, string>;
165
+ /**
166
+ * The `page` query a link selects, typed by the `$page` it came from: the strategy's members under
167
+ * their wire keys (counts as numbers, cursors as strings), plus the menu arm the read picked.
168
+ */
169
+ type PageParams<P extends Page = Page> = PageParamsOf<P>;
163
170
  /**
164
171
  * The `page` params for the page after `list`, read from its `next` link, or `undefined` on the
165
- * last page (or before anything has loaded). Works the same for page, offset and cursor
166
- * pagination, because the server's link already carries the right parameters. Feed the result
167
- * back into the signal your resource's params read:
172
+ * last page (or before anything has loaded). Works the same for every strategy, because the
173
+ * server's link already carries the right parameters. Feed the result back into the signal your
174
+ * resource's params read:
168
175
  *
169
176
  * ```ts
170
177
  * const next = nextPage(this.albums.value())
171
178
  * if (next) this.page.set(next)
172
179
  * ```
173
180
  */
174
- declare function nextPage(list: {
175
- readonly $page: Page;
176
- } | undefined): PageParams | undefined;
181
+ declare function nextPage<P extends Page>(list: {
182
+ readonly $page: P;
183
+ } | undefined): PageParams<P> | undefined;
177
184
  /** The `page` params for the page before `list`, from its `prev` link. See {@link nextPage}. */
178
- declare function prevPage(list: {
179
- readonly $page: Page;
180
- } | undefined): PageParams | undefined;
185
+ declare function prevPage<P extends Page>(list: {
186
+ readonly $page: P;
187
+ } | undefined): PageParams<P> | undefined;
181
188
  /** Options for {@link loadAll}. */
182
189
  interface LoadAllOptions {
183
190
  /** Stops the walk, rejecting with the signal's reason. Pass the loader's `abortSignal`. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/provide.ts","../src/resource.ts","../src/mutation.ts","../src/pagination.ts","../src/transport.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/provide.ts","../src/resource.ts","../src/mutation.ts","../src/pagination.ts","../src/transport.ts"],"mappings":";;;;;;;;AAgBA;KAAY,oBAAA,GAAuB,IAAA,CAAK,aAAA;EACtC,SAAA,GAAY,gBAAA;AAAA;;KAIF,oBAAA,OAA2B,OAAA,EAAS,aAAA,KAAkB,CAAC;;KAGvD,eAAA,MAAqB,CAAA,WAAW,OAAA,EAAS,aAAa,gBAAe,CAAA;;;;;;AAPnD;AAI9B;;;;;;UAkBiB,QAAA;;KAGL,gBAAA,GAAmB,QAAQ;EAAW,MAAA;AAAA,IAAoB,CAAA;;cAGzD,eAAA,EAAe,cAAA;;;;;iBAMZ,wBAAA,IAA4B,WAAA,YAAgC,cAAc,CAAC,CAAA;;;AA3BT;AAelF;;;;AAAyB;iBAwBT,oBAAA,IACd,YAAA,EAAc,oBAAA,CAAqB,CAAA,GACnC,OAAA,EAAS,oBAAA,UAA8B,oBAAA,GACvC,KAAA,GAAO,cAAA,CAAe,CAAA,IACrB,oBAAA;;;;;iBAmBa,mBAAA,KAAwB,gBAAA,EACtC,KAAA,GAAQ,cAAA,CAAe,CAAA,GACvB,OAAA;EAAY,QAAA,GAAW,QAAA;AAAA,IACtB,CAAA;;;;;AAzEH;;;KCMY,oBAAA,8BACR,YAAA,CAAa,CAAA,IACb,oBAAA,GACA,KAAA;;UAGa,oBAAA;EACf,MAAA,EAAQ,CAAA;EACR,MAAA,EAAQ,CAAA;EDdyB;ECgBjC,WAAA,EAAa,WAAA;EACb,QAAA;IAAY,MAAA,EAAQ,cAAA;EAAA;AAAA;ADZtB;AAAA,UCgBiB,sBAAA;EDhBe;;;;ECqB9B,MAAA,SAAe,CAAA;EDrBiD;ECuBhE,MAAA,GAAS,OAAA,EAAS,oBAAA,CAAqB,CAAA,EAAG,CAAA,MAAO,WAAA,CAAY,CAAA;EDvBI;ECyBjE,MAAA,GAAS,cAAA,CAAe,CAAA;EDtBC;ECwBzB,YAAA,GAAe,CAAA;EACf,KAAA,GAAQ,eAAA,CAAgB,CAAA;EDzBE;EC2B1B,QAAA,GAAW,QAAA;EACX,SAAA;AAAA;;;;AD5BgF;AAelF;;UCsBiB,kBAAA,uCAAyD,IAAA,CACxE,WAAA,CAAY,CAAA;EAAA,SAGH,KAAA,EAAO,MAAA,CAAO,oBAAA,CAAqB,CAAA;EAAA,SACnC,YAAA,EAAc,MAAA,CAAO,YAAA,CAAa,CAAA;EDxBjC;EC0BV,QAAA,CACE,IAAA,EAAM,CAAA,4CACG,kBAAA,CAAmB,OAAA,CAAQ,CAAA,cAAe,CAAA;EACrD,QAAA;AAAA;;;;;;AD7BqE;AAGvE;;;;AAA4B;AAM5B;;iBCoCgB,eAAA,qBAAoC,gBAAA,EAClD,OAAA,EAAS,sBAAA,CAAuB,CAAA,EAAG,CAAA,EAAG,CAAA;EAAO,YAAA,EAAc,CAAA;AAAA,IAC1D,kBAAA,CAAmB,CAAA,EAAG,WAAA,CAAY,CAAA;AAAA,iBACrB,eAAA,qBAAoC,gBAAA,EAClD,OAAA,EAAS,sBAAA,CAAuB,CAAA,EAAG,CAAA,EAAG,CAAA,IACrC,kBAAA,CAAmB,CAAA,cAAe,WAAA,CAAY,CAAA;;;;UC9EhC,sBAAA;EFEe;EEA9B,MAAA,GAAS,cAAA,CAAe,CAAA;EFAc;EEEtC,QAAA,GAAW,QAAA;AAAA;;;;;UAOI,kBAAA;EFRH;;AAAgB;AAI9B;EESE,MAAA,CAAO,KAAA,EAAO,CAAA,GAAI,OAAA,CAAQ,CAAA;EAAA,SACjB,SAAA,EAAW,MAAA;EAAA,SACX,KAAA,EAAO,MAAA,CAAO,oBAAA,CAAqB,CAAA;EFXb;EAAA,SEatB,YAAA,EAAc,MAAA,CAAO,YAAA,CAAa,CAAA;EFbN;EAAA,SEe5B,KAAA,EAAO,MAAA,CAAO,CAAA;EFf0C;EEiBjE,KAAA;AAAA;;;;;;;;;;;AFdgF;iBE4BlE,eAAA,WAA0B,gBAAA,EACxC,QAAA,GAAW,MAAA,EAAQ,CAAA,EAAG,KAAA,EAAO,CAAA,KAAM,WAAA,CAAY,CAAA,GAC/C,OAAA,GAAS,sBAAA,CAAuB,CAAA,IAC/B,kBAAA,CAAmB,CAAA,EAAG,CAAA,EAAG,WAAA,CAAY,CAAA;;;;;;;KCjD5B,UAAA,WAAqB,IAAA,GAAO,IAAA,IAAQ,YAAA,CAAa,CAAA;;;;;;;;;;;;iBAa7C,QAAA,WAAmB,IAAA,EACjC,IAAA;EAAA,SAAiB,KAAA,EAAO,CAAA;AAAA,gBACvB,UAAA,CAAW,CAAA;;iBAKE,QAAA,WAAmB,IAAA,EACjC,IAAA;EAAA,SAAiB,KAAA,EAAO,CAAA;AAAA,gBACvB,UAAA,CAAW,CAAA;;UAgCG,cAAA;EHvCiD;EGyChE,MAAA,GAAS,WAAW;EHzC6C;EG2CjE,QAAA;AAAA;;;;;;;;;;iBAYoB,OAAA,IACpB,KAAA,EAAO,UAAA,CAAW,CAAA,IAAK,WAAA,CAAY,UAAA,CAAW,CAAA,IAC9C,OAAA,GAAS,cAAA,GACR,OAAA,CAAQ,CAAA;;;;;;AH/DX;;;;;;iBIJgB,mBAAA,CAAoB,IAAA,EAAM,UAAA,GAAa,gBAAgB"}
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { EnvironmentProviders, InjectionToken, Injector, ResourceRef, ResourceStatus, Signal, ValueEqualityFn } from "@angular/core";
2
- import { ClientOptions, Collection, JsonApiError, JsonApiTransport, Page, StructuralGuardError } from "@haddowg/json-api-client";
2
+ import { ClientOptions, Collection, ErrorCodeOf, JsonApiError, JsonApiTransport, Page, PageParamsOf, StructuralGuardError } from "@haddowg/json-api-client";
3
3
  import { HttpClient } from "@angular/common/http";
4
4
 
5
5
  //#region src/provide.d.ts
@@ -59,9 +59,9 @@ declare function injectJsonApiClient<C = RegisteredClient>(token?: InjectionToke
59
59
  /**
60
60
  * What a read can fail with: a `JsonApiError` for a non-2xx response, a `StructuralGuardError`
61
61
  * for a response that isn't a JSON:API document, or any other error (a network failure, a
62
- * validator's error). Narrow with `instanceof`.
62
+ * validator's error). Narrow with `instanceof`. `E` types the `JsonApiError`'s codes.
63
63
  */
64
- type JsonApiResourceError = JsonApiError | StructuralGuardError | Error;
64
+ type JsonApiResourceError<E extends string = string> = JsonApiError<E> | StructuralGuardError | Error;
65
65
  /** What the loader receives each time the params change or the resource reloads. */
66
66
  interface JsonApiLoaderContext<C, P> {
67
67
  client: C;
@@ -93,13 +93,14 @@ interface JsonApiResourceOptions<T, P, C> {
93
93
  /**
94
94
  * Angular's `ResourceRef` with `error()` typed to what a JSON:API read throws, plus
95
95
  * `jsonApiError()`, the error when it is a `JsonApiError` (handy in templates, which can't
96
- * use `instanceof`).
96
+ * use `instanceof`). `E` is the client's error-code union, so a generated client's
97
+ * `jsonApiError()` carries its server's `ErrorCode`.
97
98
  */
98
- interface JsonApiResourceRef<T> extends Omit<ResourceRef<T>, 'error' | 'hasValue'> {
99
- readonly error: Signal<JsonApiResourceError | undefined>;
100
- readonly jsonApiError: Signal<JsonApiError | undefined>;
99
+ interface JsonApiResourceRef<T, E extends string = string> extends Omit<ResourceRef<T>, 'error' | 'hasValue'> {
100
+ readonly error: Signal<JsonApiResourceError<E> | undefined>;
101
+ readonly jsonApiError: Signal<JsonApiError<E> | undefined>;
101
102
  /** Whether a value is loaded. Narrows `value()` to exclude `undefined`, in templates too. */
102
- hasValue(this: T extends undefined ? this : never): this is JsonApiResourceRef<Exclude<T, undefined>>;
103
+ hasValue(this: T extends undefined ? this : never): this is JsonApiResourceRef<Exclude<T, undefined>, E>;
103
104
  hasValue(): boolean;
104
105
  }
105
106
  /**
@@ -117,8 +118,8 @@ interface JsonApiResourceRef<T> extends Omit<ResourceRef<T>, 'error' | 'hasValue
117
118
  */
118
119
  declare function jsonApiResource<T, P = unknown, C = RegisteredClient>(options: JsonApiResourceOptions<T, P, C> & {
119
120
  defaultValue: T;
120
- }): JsonApiResourceRef<T>;
121
- declare function jsonApiResource<T, P = unknown, C = RegisteredClient>(options: JsonApiResourceOptions<T, P, C>): JsonApiResourceRef<T | undefined>;
121
+ }): JsonApiResourceRef<T, ErrorCodeOf<C>>;
122
+ declare function jsonApiResource<T, P = unknown, C = RegisteredClient>(options: JsonApiResourceOptions<T, P, C>): JsonApiResourceRef<T | undefined, ErrorCodeOf<C>>;
122
123
  //#endregion
123
124
  //#region src/mutation.d.ts
124
125
  /** Options for {@link jsonApiMutation}. */
@@ -128,17 +129,20 @@ interface JsonApiMutationOptions<C> {
128
129
  /** Create the mutation outside an injection context. */
129
130
  injector?: Injector;
130
131
  }
131
- /** The signal-based handle {@link jsonApiMutation} returns. */
132
- interface JsonApiMutationRef<I, T> {
132
+ /**
133
+ * The signal-based handle {@link jsonApiMutation} returns. `E` is the client's error-code union,
134
+ * so a generated client's `jsonApiError()` carries its server's `ErrorCode`.
135
+ */
136
+ interface JsonApiMutationRef<I, T, E extends string = string> {
133
137
  /**
134
138
  * Run the write. Resolves with its result and rejects with its error; the signals track the
135
139
  * latest call either way.
136
140
  */
137
141
  mutate(input: I): Promise<T>;
138
142
  readonly isPending: Signal<boolean>;
139
- readonly error: Signal<JsonApiResourceError | undefined>;
143
+ readonly error: Signal<JsonApiResourceError<E> | undefined>;
140
144
  /** The error when it is a `JsonApiError`; use `byPath()` on it for form errors. */
141
- readonly jsonApiError: Signal<JsonApiError | undefined>;
145
+ readonly jsonApiError: Signal<JsonApiError<E> | undefined>;
142
146
  /** The latest successful result. */
143
147
  readonly value: Signal<T | undefined>;
144
148
  /** Clear the value and error. A call still in flight no longer updates the signals. */
@@ -155,29 +159,32 @@ interface JsonApiMutationRef<I, T> {
155
159
  * )
156
160
  * ```
157
161
  */
158
- declare function jsonApiMutation<I, T, C = RegisteredClient>(mutation: (client: C, input: I) => PromiseLike<T>, options?: JsonApiMutationOptions<C>): JsonApiMutationRef<I, T>;
162
+ declare function jsonApiMutation<I, T, C = RegisteredClient>(mutation: (client: C, input: I) => PromiseLike<T>, options?: JsonApiMutationOptions<C>): JsonApiMutationRef<I, T, ErrorCodeOf<C>>;
159
163
  //#endregion
160
164
  //#region src/pagination.d.ts
161
- /** The `page` query a link selects: its `page[...]` parameters, keyed without the prefix. */
162
- type PageParams = Record<string, string>;
165
+ /**
166
+ * The `page` query a link selects, typed by the `$page` it came from: the strategy's members under
167
+ * their wire keys (counts as numbers, cursors as strings), plus the menu arm the read picked.
168
+ */
169
+ type PageParams<P extends Page = Page> = PageParamsOf<P>;
163
170
  /**
164
171
  * The `page` params for the page after `list`, read from its `next` link, or `undefined` on the
165
- * last page (or before anything has loaded). Works the same for page, offset and cursor
166
- * pagination, because the server's link already carries the right parameters. Feed the result
167
- * back into the signal your resource's params read:
172
+ * last page (or before anything has loaded). Works the same for every strategy, because the
173
+ * server's link already carries the right parameters. Feed the result back into the signal your
174
+ * resource's params read:
168
175
  *
169
176
  * ```ts
170
177
  * const next = nextPage(this.albums.value())
171
178
  * if (next) this.page.set(next)
172
179
  * ```
173
180
  */
174
- declare function nextPage(list: {
175
- readonly $page: Page;
176
- } | undefined): PageParams | undefined;
181
+ declare function nextPage<P extends Page>(list: {
182
+ readonly $page: P;
183
+ } | undefined): PageParams<P> | undefined;
177
184
  /** The `page` params for the page before `list`, from its `prev` link. See {@link nextPage}. */
178
- declare function prevPage(list: {
179
- readonly $page: Page;
180
- } | undefined): PageParams | undefined;
185
+ declare function prevPage<P extends Page>(list: {
186
+ readonly $page: P;
187
+ } | undefined): PageParams<P> | undefined;
181
188
  /** Options for {@link loadAll}. */
182
189
  interface LoadAllOptions {
183
190
  /** Stops the walk, rejecting with the signal's reason. Pass the loader's `abortSignal`. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/provide.ts","../src/resource.ts","../src/mutation.ts","../src/pagination.ts","../src/transport.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/provide.ts","../src/resource.ts","../src/mutation.ts","../src/pagination.ts","../src/transport.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -165,9 +165,9 @@ function jsonApiMutation(mutation, options = {}) {
165
165
  //#region src/pagination.ts
166
166
  /**
167
167
  * The `page` params for the page after `list`, read from its `next` link, or `undefined` on the
168
- * last page (or before anything has loaded). Works the same for page, offset and cursor
169
- * pagination, because the server's link already carries the right parameters. Feed the result
170
- * back into the signal your resource's params read:
168
+ * last page (or before anything has loaded). Works the same for every strategy, because the
169
+ * server's link already carries the right parameters. Feed the result back into the signal your
170
+ * resource's params read:
171
171
  *
172
172
  * ```ts
173
173
  * const next = nextPage(this.albums.value())
@@ -175,19 +175,24 @@ function jsonApiMutation(mutation, options = {}) {
175
175
  * ```
176
176
  */
177
177
  function nextPage(list) {
178
- return pageParams(list?.$page.links.next);
178
+ return pageParams(list?.$page, list?.$page.links.next);
179
179
  }
180
180
  /** The `page` params for the page before `list`, from its `prev` link. See {@link nextPage}. */
181
181
  function prevPage(list) {
182
- return pageParams(list?.$page.links.prev);
182
+ return pageParams(list?.$page, list?.$page.links.prev);
183
183
  }
184
- function pageParams(link) {
185
- if (link === void 0) return;
184
+ const CURSOR_ROLES = ["after", "before"];
185
+ function pageParams(page, link) {
186
+ if (page === void 0 || link === void 0) return;
187
+ const keys = Object.entries(page.keys);
186
188
  const query = link.includes("?") ? link.slice(link.indexOf("?") + 1).split("#")[0] : "";
187
189
  const params = {};
188
- for (const [key, value] of new URLSearchParams(query)) {
189
- const match = /^page\[([^\]]+)\]$/.exec(key);
190
- if (match?.[1] !== void 0) params[match[1]] = value;
190
+ for (const [name, value] of new URLSearchParams(query)) {
191
+ const member = /^page\[([^\]]+)\]$/.exec(name)?.[1];
192
+ if (member === void 0) continue;
193
+ const role = keys.find(([, key]) => key === member)?.[0];
194
+ const count = Number(value);
195
+ params[member] = role !== void 0 && !CURSOR_ROLES.includes(role) && value !== "" && Number.isFinite(count) ? count : value;
191
196
  }
192
197
  return params;
193
198
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/transport.ts","../src/provide.ts","../src/mutation.ts","../src/pagination.ts","../src/resource.ts"],"sourcesContent":["import { HttpErrorResponse, type HttpClient, type HttpHeaders } from '@angular/common/http'\nimport type { JsonApiTransport, TransportResponse } from '@haddowg/json-api-client'\n\n/**\n * A transport that sends every request through Angular's `HttpClient`, so the app's interceptors\n * run. The body comes back as text for the client to parse, exactly as with `fetchTransport`.\n *\n * A non-2xx response resolves with its status, headers and body, because the client maps those to\n * a `JsonApiError` itself. Only a request that never got a response (status 0: network failure,\n * CORS, a blocked request) rejects, with the `HttpErrorResponse`. Aborting `req.signal`\n * unsubscribes, which cancels the HTTP request, and rejects with the signal's reason.\n */\nexport function httpClientTransport(http: HttpClient): JsonApiTransport {\n return (req) =>\n new Promise<TransportResponse>((resolve, reject) => {\n const signal = req.signal\n if (signal?.aborted) {\n reject(signal.reason)\n return\n }\n\n let unsubscribe: (() => void) | undefined\n const onAbort = (): void => {\n unsubscribe?.()\n reject(signal?.reason)\n }\n signal?.addEventListener('abort', onAbort, { once: true })\n const settle = (): void => signal?.removeEventListener('abort', onAbort)\n\n const subscription = http\n .request(req.method, req.url, {\n body: req.body ?? null,\n headers: req.headers,\n observe: 'response',\n responseType: 'text',\n })\n .subscribe({\n next: (res) => {\n settle()\n resolve({ status: res.status, headers: toRecord(res.headers), body: res.body ?? '' })\n },\n error: (error: unknown) => {\n settle()\n if (error instanceof HttpErrorResponse && error.status !== 0) {\n resolve({\n status: error.status,\n headers: toRecord(error.headers),\n body: errorBody(error.error),\n })\n return\n }\n reject(error)\n },\n })\n unsubscribe = () => subscription.unsubscribe()\n })\n}\n\nfunction toRecord(headers: HttpHeaders): Record<string, string> {\n const out: Record<string, string> = {}\n for (const name of headers.keys()) {\n const values = headers.getAll(name)\n if (values !== null) {\n out[name.toLowerCase()] = values.join(', ')\n }\n }\n return out\n}\n\n// With responseType 'text' the error body is already a string. Anything else (an interceptor that\n// replaced it) is serialised so the client can still try to parse a JSON:API error document.\nfunction errorBody(body: unknown): string {\n if (typeof body === 'string') {\n return body\n }\n if (body === null || body === undefined) {\n return ''\n }\n return JSON.stringify(body)\n}\n","import { HttpClient } from '@angular/common/http'\nimport {\n assertInInjectionContext,\n type EnvironmentProviders,\n inject,\n InjectionToken,\n type Injector,\n makeEnvironmentProviders,\n} from '@angular/core'\nimport type { ClientOptions, JsonApiTransport } from '@haddowg/json-api-client'\nimport { httpClientTransport } from './transport'\n\n/**\n * Options for {@link provideJsonApiClient}: the client's options with the transport made\n * optional. Leave `transport` out to send through `HttpClient`; set it only to opt out.\n */\nexport type JsonApiClientOptions = Omit<ClientOptions, 'transport'> & {\n transport?: JsonApiTransport\n}\n\n/** A generated client factory: the bound `createClient` a codegen module exports. */\nexport type JsonApiClientFactory<C> = (options: ClientOptions) => C\n\n/** The client type a generated `createClient` returns. */\nexport type JsonApiClientOf<F> = F extends (options: ClientOptions) => infer C ? C : never\n\n/**\n * Register the app's default client type once, so `injectJsonApiClient()` and `jsonApiResource()`\n * are typed without a token:\n *\n * ```ts\n * declare module '@haddowg/json-api-angular' {\n * interface Register {\n * client: JsonApiClientOf<typeof createClient>\n * }\n * }\n * ```\n */\n// oxlint-disable-next-line typescript/no-empty-object-type -- filled in by module augmentation\nexport interface Register {}\n\n/** The default client's type: whatever {@link Register} declares, else `unknown`. */\nexport type RegisteredClient = Register extends { client: infer C } ? C : unknown\n\n/** The token {@link provideJsonApiClient} uses when none is given. */\nexport const JSON_API_CLIENT = new InjectionToken<unknown>('JSON_API_CLIENT')\n\n/**\n * Create an injection token for one client, so an app can provide several (one per server).\n * Type it with the client: `createJsonApiClientToken<JsonApiClientOf<typeof createAdminClient>>()`.\n */\nexport function createJsonApiClientToken<C>(description = 'JsonApiClient'): InjectionToken<C> {\n return new InjectionToken<C>(description)\n}\n\n/**\n * Provide a generated client to the environment injector. The client sends through `HttpClient`\n * (so `provideHttpClient()` must also be present) unless `options.transport` overrides it.\n *\n * `options` may be a function, which runs in an injection context, to read values such as the\n * base URL from other providers. `token` defaults to {@link JSON_API_CLIENT}; pass one from\n * {@link createJsonApiClientToken} to provide a second client alongside it.\n */\nexport function provideJsonApiClient<C>(\n createClient: JsonApiClientFactory<C>,\n options: JsonApiClientOptions | (() => JsonApiClientOptions),\n token: InjectionToken<C> = JSON_API_CLIENT as InjectionToken<C>,\n): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: token,\n useFactory: (): C => {\n const resolved = typeof options === 'function' ? options() : options\n return createClient({\n ...resolved,\n transport: resolved.transport ?? httpClientTransport(inject(HttpClient)),\n })\n },\n },\n ])\n}\n\n/**\n * Inject a provided client. Without a token this returns the default client, typed by\n * {@link Register}. With a token it returns that token's client.\n */\nexport function injectJsonApiClient<C = RegisteredClient>(\n token?: InjectionToken<C>,\n options?: { injector?: Injector },\n): C {\n const resolved = token ?? (JSON_API_CLIENT as InjectionToken<C>)\n if (options?.injector !== undefined) {\n return options.injector.get(resolved)\n }\n assertInInjectionContext(injectJsonApiClient)\n return inject(resolved)\n}\n","import {\n assertInInjectionContext,\n computed,\n inject,\n type InjectionToken,\n Injector,\n type Signal,\n signal,\n} from '@angular/core'\nimport { JsonApiError } from '@haddowg/json-api-client'\nimport { JSON_API_CLIENT, type RegisteredClient } from './provide'\nimport type { JsonApiResourceError } from './resource'\n\n/** Options for {@link jsonApiMutation}. */\nexport interface JsonApiMutationOptions<C> {\n /** The client to write with. Defaults to the one `provideJsonApiClient` registered without a token. */\n client?: InjectionToken<C>\n /** Create the mutation outside an injection context. */\n injector?: Injector\n}\n\n/** The signal-based handle {@link jsonApiMutation} returns. */\nexport interface JsonApiMutationRef<I, T> {\n /**\n * Run the write. Resolves with its result and rejects with its error; the signals track the\n * latest call either way.\n */\n mutate(input: I): Promise<T>\n readonly isPending: Signal<boolean>\n readonly error: Signal<JsonApiResourceError | undefined>\n /** The error when it is a `JsonApiError`; use `byPath()` on it for form errors. */\n readonly jsonApiError: Signal<JsonApiError | undefined>\n /** The latest successful result. */\n readonly value: Signal<T | undefined>\n /** Clear the value and error. A call still in flight no longer updates the signals. */\n reset(): void\n}\n\n/**\n * Wrap a client write (create, update, delete, a relationship mutation, a custom action or an\n * atomic batch) in signals. Nothing is cached or invalidated: `reload()` the resources the write\n * affects.\n *\n * ```ts\n * readonly rename = jsonApiMutation((client, title: string) =>\n * client.albums.id(this.id()).update({ title }),\n * )\n * ```\n */\nexport function jsonApiMutation<I, T, C = RegisteredClient>(\n mutation: (client: C, input: I) => PromiseLike<T>,\n options: JsonApiMutationOptions<C> = {},\n): JsonApiMutationRef<I, T> {\n if (options.injector === undefined) {\n assertInInjectionContext(jsonApiMutation)\n }\n const injector = options.injector ?? inject(Injector)\n const client = injector.get(options.client ?? (JSON_API_CLIENT as InjectionToken<C>))\n\n const value = signal<T | undefined>(undefined)\n const error = signal<JsonApiResourceError | undefined>(undefined)\n const pending = signal(0)\n // Each call and reset bumps this, so only the latest call writes to the signals.\n let generation = 0\n\n const mutate = async (input: I): Promise<T> => {\n const current = ++generation\n pending.update((n) => n + 1)\n error.set(undefined)\n try {\n const result = await mutation(client, input)\n if (current === generation) {\n value.set(result)\n }\n return result\n } catch (thrown) {\n if (current === generation) {\n error.set(thrown instanceof Error ? thrown : new Error(String(thrown), { cause: thrown }))\n }\n throw thrown\n } finally {\n pending.update((n) => Math.max(0, n - 1))\n }\n }\n\n return {\n mutate,\n isPending: computed(() => pending() > 0),\n error: error.asReadonly(),\n jsonApiError: computed(() => {\n const e = error()\n return e instanceof JsonApiError ? e : undefined\n }),\n value: value.asReadonly(),\n reset: () => {\n generation++\n value.set(undefined)\n error.set(undefined)\n pending.set(0)\n },\n }\n}\n","import type { Collection, Page } from '@haddowg/json-api-client'\n\n/** The `page` query a link selects: its `page[...]` parameters, keyed without the prefix. */\nexport type PageParams = Record<string, string>\n\n/**\n * The `page` params for the page after `list`, read from its `next` link, or `undefined` on the\n * last page (or before anything has loaded). Works the same for page, offset and cursor\n * pagination, because the server's link already carries the right parameters. Feed the result\n * back into the signal your resource's params read:\n *\n * ```ts\n * const next = nextPage(this.albums.value())\n * if (next) this.page.set(next)\n * ```\n */\nexport function nextPage(list: { readonly $page: Page } | undefined): PageParams | undefined {\n return pageParams(list?.$page.links.next)\n}\n\n/** The `page` params for the page before `list`, from its `prev` link. See {@link nextPage}. */\nexport function prevPage(list: { readonly $page: Page } | undefined): PageParams | undefined {\n return pageParams(list?.$page.links.prev)\n}\n\nfunction pageParams(link: string | undefined): PageParams | undefined {\n if (link === undefined) {\n return undefined\n }\n const query = link.includes('?') ? link.slice(link.indexOf('?') + 1).split('#')[0] : ''\n const params: PageParams = {}\n for (const [key, value] of new URLSearchParams(query)) {\n const match = /^page\\[([^\\]]+)\\]$/.exec(key)\n if (match?.[1] !== undefined) {\n params[match[1]] = value\n }\n }\n return params\n}\n\n/** Options for {@link loadAll}. */\nexport interface LoadAllOptions {\n /** Stops the walk, rejecting with the signal's reason. Pass the loader's `abortSignal`. */\n signal?: AbortSignal\n /** Stop after this many pages, as a guard against an unexpectedly large collection. */\n maxPages?: number\n}\n\n/**\n * Follow `next` links from the first page until the last, and return every member in one plain\n * array. Use it inside a loader to read a whole (small) collection:\n *\n * ```ts\n * loader: ({ client, abortSignal }) =>\n * loadAll(client.genres.list({ signal: abortSignal }), { signal: abortSignal })\n * ```\n */\nexport async function loadAll<T>(\n first: Collection<T> | PromiseLike<Collection<T>>,\n options: LoadAllOptions = {},\n): Promise<T[]> {\n const { signal, maxPages = Number.POSITIVE_INFINITY } = options\n let page: Collection<T> | undefined = await first\n const all: T[] = []\n let count = 0\n while (page !== undefined && count < maxPages) {\n all.push(...page)\n count++\n if (count >= maxPages) {\n break\n }\n signal?.throwIfAborted()\n page = await page.$next(signal !== undefined ? { signal } : undefined)\n }\n return all\n}\n","// The only module that touches Angular's `resource()`. It is experimental before Angular 22, so a\n// change to its options or ref shape is absorbed here.\nimport {\n assertInInjectionContext,\n computed,\n inject,\n type InjectionToken,\n Injector,\n resource,\n type ResourceRef,\n type ResourceStatus,\n type Signal,\n type ValueEqualityFn,\n} from '@angular/core'\nimport { JsonApiError, type StructuralGuardError } from '@haddowg/json-api-client'\nimport { JSON_API_CLIENT, type RegisteredClient } from './provide'\n\n/**\n * What a read can fail with: a `JsonApiError` for a non-2xx response, a `StructuralGuardError`\n * for a response that isn't a JSON:API document, or any other error (a network failure, a\n * validator's error). Narrow with `instanceof`.\n */\nexport type JsonApiResourceError = JsonApiError | StructuralGuardError | Error\n\n/** What the loader receives each time the params change or the resource reloads. */\nexport interface JsonApiLoaderContext<C, P> {\n client: C\n params: P\n /** Aborts when the params change again or the resource is destroyed. Pass it to the read. */\n abortSignal: AbortSignal\n previous: { status: ResourceStatus }\n}\n\n/** Options for {@link jsonApiResource}. */\nexport interface JsonApiResourceOptions<T, P, C> {\n /**\n * A reactive function computing the read's params from signals. Return `undefined` while the\n * read isn't ready (no id yet, say) and the resource stays idle. Leave it out to load once.\n */\n params?: () => P | undefined\n /** Make the read. Return the client call's promise, passing `abortSignal` as its `signal`. */\n loader: (context: JsonApiLoaderContext<C, P>) => PromiseLike<T>\n /** The client to read with. Defaults to the one `provideJsonApiClient` registered without a token. */\n client?: InjectionToken<C>\n /** The value while nothing has loaded yet. */\n defaultValue?: T\n equal?: ValueEqualityFn<T>\n /** Create the resource outside an injection context. */\n injector?: Injector\n debugName?: string\n}\n\n/**\n * Angular's `ResourceRef` with `error()` typed to what a JSON:API read throws, plus\n * `jsonApiError()`, the error when it is a `JsonApiError` (handy in templates, which can't\n * use `instanceof`).\n */\nexport interface JsonApiResourceRef<T> extends Omit<ResourceRef<T>, 'error' | 'hasValue'> {\n readonly error: Signal<JsonApiResourceError | undefined>\n readonly jsonApiError: Signal<JsonApiError | undefined>\n /** Whether a value is loaded. Narrows `value()` to exclude `undefined`, in templates too. */\n hasValue(\n this: T extends undefined ? this : never,\n ): this is JsonApiResourceRef<Exclude<T, undefined>>\n hasValue(): boolean\n}\n\n/**\n * A signal-based JSON:API read: a thin wrapper over Angular's `resource()` that injects the\n * client and hands it to the loader. Changing a signal the params read starts a new load and\n * aborts the one in flight.\n *\n * ```ts\n * readonly albums = jsonApiResource({\n * params: () => ({ filter: { title: this.search() } }),\n * loader: ({ client, params, abortSignal }) =>\n * client.albums.list({ ...params, include: ['artist'], signal: abortSignal }),\n * })\n * ```\n */\nexport function jsonApiResource<T, P = unknown, C = RegisteredClient>(\n options: JsonApiResourceOptions<T, P, C> & { defaultValue: T },\n): JsonApiResourceRef<T>\nexport function jsonApiResource<T, P = unknown, C = RegisteredClient>(\n options: JsonApiResourceOptions<T, P, C>,\n): JsonApiResourceRef<T | undefined>\nexport function jsonApiResource<T, P, C>(\n options: JsonApiResourceOptions<T, P, C>,\n): JsonApiResourceRef<T | undefined> {\n if (options.injector === undefined) {\n assertInInjectionContext(jsonApiResource)\n }\n const injector = options.injector ?? inject(Injector)\n const client = injector.get(options.client ?? (JSON_API_CLIENT as InjectionToken<C>))\n\n const ref = resource<T | undefined, P | undefined>({\n ...(options.params !== undefined ? { params: options.params } : {}),\n loader: ({ params, abortSignal, previous }) =>\n options.loader({ client, params: params as P, abortSignal, previous }),\n ...(options.defaultValue !== undefined ? { defaultValue: options.defaultValue } : {}),\n ...(options.equal !== undefined\n ? { equal: options.equal as ValueEqualityFn<T | undefined> }\n : {}),\n ...(options.debugName !== undefined ? { debugName: options.debugName } : {}),\n injector,\n })\n\n const jsonApiError = computed(() => {\n const error = ref.error()\n return error instanceof JsonApiError ? error : undefined\n })\n Object.defineProperty(ref, 'jsonApiError', { value: jsonApiError, enumerable: true })\n return ref as unknown as JsonApiResourceRef<T | undefined>\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/transport.ts","../src/provide.ts","../src/mutation.ts","../src/pagination.ts","../src/resource.ts"],"sourcesContent":["import { HttpErrorResponse, type HttpClient, type HttpHeaders } from '@angular/common/http'\nimport type { JsonApiTransport, TransportResponse } from '@haddowg/json-api-client'\n\n/**\n * A transport that sends every request through Angular's `HttpClient`, so the app's interceptors\n * run. The body comes back as text for the client to parse, exactly as with `fetchTransport`.\n *\n * A non-2xx response resolves with its status, headers and body, because the client maps those to\n * a `JsonApiError` itself. Only a request that never got a response (status 0: network failure,\n * CORS, a blocked request) rejects, with the `HttpErrorResponse`. Aborting `req.signal`\n * unsubscribes, which cancels the HTTP request, and rejects with the signal's reason.\n */\nexport function httpClientTransport(http: HttpClient): JsonApiTransport {\n return (req) =>\n new Promise<TransportResponse>((resolve, reject) => {\n const signal = req.signal\n if (signal?.aborted) {\n reject(signal.reason)\n return\n }\n\n let unsubscribe: (() => void) | undefined\n const onAbort = (): void => {\n unsubscribe?.()\n reject(signal?.reason)\n }\n signal?.addEventListener('abort', onAbort, { once: true })\n const settle = (): void => signal?.removeEventListener('abort', onAbort)\n\n const subscription = http\n .request(req.method, req.url, {\n body: req.body ?? null,\n headers: req.headers,\n observe: 'response',\n responseType: 'text',\n })\n .subscribe({\n next: (res) => {\n settle()\n resolve({ status: res.status, headers: toRecord(res.headers), body: res.body ?? '' })\n },\n error: (error: unknown) => {\n settle()\n if (error instanceof HttpErrorResponse && error.status !== 0) {\n resolve({\n status: error.status,\n headers: toRecord(error.headers),\n body: errorBody(error.error),\n })\n return\n }\n reject(error)\n },\n })\n unsubscribe = () => subscription.unsubscribe()\n })\n}\n\nfunction toRecord(headers: HttpHeaders): Record<string, string> {\n const out: Record<string, string> = {}\n for (const name of headers.keys()) {\n const values = headers.getAll(name)\n if (values !== null) {\n out[name.toLowerCase()] = values.join(', ')\n }\n }\n return out\n}\n\n// With responseType 'text' the error body is already a string. Anything else (an interceptor that\n// replaced it) is serialised so the client can still try to parse a JSON:API error document.\nfunction errorBody(body: unknown): string {\n if (typeof body === 'string') {\n return body\n }\n if (body === null || body === undefined) {\n return ''\n }\n return JSON.stringify(body)\n}\n","import { HttpClient } from '@angular/common/http'\nimport {\n assertInInjectionContext,\n type EnvironmentProviders,\n inject,\n InjectionToken,\n type Injector,\n makeEnvironmentProviders,\n} from '@angular/core'\nimport type { ClientOptions, JsonApiTransport } from '@haddowg/json-api-client'\nimport { httpClientTransport } from './transport'\n\n/**\n * Options for {@link provideJsonApiClient}: the client's options with the transport made\n * optional. Leave `transport` out to send through `HttpClient`; set it only to opt out.\n */\nexport type JsonApiClientOptions = Omit<ClientOptions, 'transport'> & {\n transport?: JsonApiTransport\n}\n\n/** A generated client factory: the bound `createClient` a codegen module exports. */\nexport type JsonApiClientFactory<C> = (options: ClientOptions) => C\n\n/** The client type a generated `createClient` returns. */\nexport type JsonApiClientOf<F> = F extends (options: ClientOptions) => infer C ? C : never\n\n/**\n * Register the app's default client type once, so `injectJsonApiClient()` and `jsonApiResource()`\n * are typed without a token:\n *\n * ```ts\n * declare module '@haddowg/json-api-angular' {\n * interface Register {\n * client: JsonApiClientOf<typeof createClient>\n * }\n * }\n * ```\n */\n// oxlint-disable-next-line typescript/no-empty-object-type -- filled in by module augmentation\nexport interface Register {}\n\n/** The default client's type: whatever {@link Register} declares, else `unknown`. */\nexport type RegisteredClient = Register extends { client: infer C } ? C : unknown\n\n/** The token {@link provideJsonApiClient} uses when none is given. */\nexport const JSON_API_CLIENT = new InjectionToken<unknown>('JSON_API_CLIENT')\n\n/**\n * Create an injection token for one client, so an app can provide several (one per server).\n * Type it with the client: `createJsonApiClientToken<JsonApiClientOf<typeof createAdminClient>>()`.\n */\nexport function createJsonApiClientToken<C>(description = 'JsonApiClient'): InjectionToken<C> {\n return new InjectionToken<C>(description)\n}\n\n/**\n * Provide a generated client to the environment injector. The client sends through `HttpClient`\n * (so `provideHttpClient()` must also be present) unless `options.transport` overrides it.\n *\n * `options` may be a function, which runs in an injection context, to read values such as the\n * base URL from other providers. `token` defaults to {@link JSON_API_CLIENT}; pass one from\n * {@link createJsonApiClientToken} to provide a second client alongside it.\n */\nexport function provideJsonApiClient<C>(\n createClient: JsonApiClientFactory<C>,\n options: JsonApiClientOptions | (() => JsonApiClientOptions),\n token: InjectionToken<C> = JSON_API_CLIENT as InjectionToken<C>,\n): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: token,\n useFactory: (): C => {\n const resolved = typeof options === 'function' ? options() : options\n return createClient({\n ...resolved,\n transport: resolved.transport ?? httpClientTransport(inject(HttpClient)),\n })\n },\n },\n ])\n}\n\n/**\n * Inject a provided client. Without a token this returns the default client, typed by\n * {@link Register}. With a token it returns that token's client.\n */\nexport function injectJsonApiClient<C = RegisteredClient>(\n token?: InjectionToken<C>,\n options?: { injector?: Injector },\n): C {\n const resolved = token ?? (JSON_API_CLIENT as InjectionToken<C>)\n if (options?.injector !== undefined) {\n return options.injector.get(resolved)\n }\n assertInInjectionContext(injectJsonApiClient)\n return inject(resolved)\n}\n","import {\n assertInInjectionContext,\n computed,\n inject,\n type InjectionToken,\n Injector,\n type Signal,\n signal,\n} from '@angular/core'\nimport { type ErrorCodeOf, JsonApiError } from '@haddowg/json-api-client'\nimport { JSON_API_CLIENT, type RegisteredClient } from './provide'\nimport type { JsonApiResourceError } from './resource'\n\n/** Options for {@link jsonApiMutation}. */\nexport interface JsonApiMutationOptions<C> {\n /** The client to write with. Defaults to the one `provideJsonApiClient` registered without a token. */\n client?: InjectionToken<C>\n /** Create the mutation outside an injection context. */\n injector?: Injector\n}\n\n/**\n * The signal-based handle {@link jsonApiMutation} returns. `E` is the client's error-code union,\n * so a generated client's `jsonApiError()` carries its server's `ErrorCode`.\n */\nexport interface JsonApiMutationRef<I, T, E extends string = string> {\n /**\n * Run the write. Resolves with its result and rejects with its error; the signals track the\n * latest call either way.\n */\n mutate(input: I): Promise<T>\n readonly isPending: Signal<boolean>\n readonly error: Signal<JsonApiResourceError<E> | undefined>\n /** The error when it is a `JsonApiError`; use `byPath()` on it for form errors. */\n readonly jsonApiError: Signal<JsonApiError<E> | undefined>\n /** The latest successful result. */\n readonly value: Signal<T | undefined>\n /** Clear the value and error. A call still in flight no longer updates the signals. */\n reset(): void\n}\n\n/**\n * Wrap a client write (create, update, delete, a relationship mutation, a custom action or an\n * atomic batch) in signals. Nothing is cached or invalidated: `reload()` the resources the write\n * affects.\n *\n * ```ts\n * readonly rename = jsonApiMutation((client, title: string) =>\n * client.albums.id(this.id()).update({ title }),\n * )\n * ```\n */\nexport function jsonApiMutation<I, T, C = RegisteredClient>(\n mutation: (client: C, input: I) => PromiseLike<T>,\n options: JsonApiMutationOptions<C> = {},\n): JsonApiMutationRef<I, T, ErrorCodeOf<C>> {\n if (options.injector === undefined) {\n assertInInjectionContext(jsonApiMutation)\n }\n const injector = options.injector ?? inject(Injector)\n const client = injector.get(options.client ?? (JSON_API_CLIENT as InjectionToken<C>))\n\n const value = signal<T | undefined>(undefined)\n const error = signal<JsonApiResourceError | undefined>(undefined)\n const pending = signal(0)\n // Each call and reset bumps this, so only the latest call writes to the signals.\n let generation = 0\n\n const mutate = async (input: I): Promise<T> => {\n const current = ++generation\n pending.update((n) => n + 1)\n error.set(undefined)\n try {\n const result = await mutation(client, input)\n if (current === generation) {\n value.set(result)\n }\n return result\n } catch (thrown) {\n if (current === generation) {\n error.set(thrown instanceof Error ? thrown : new Error(String(thrown), { cause: thrown }))\n }\n throw thrown\n } finally {\n pending.update((n) => Math.max(0, n - 1))\n }\n }\n\n return {\n mutate,\n isPending: computed(() => pending() > 0),\n error: error.asReadonly(),\n jsonApiError: computed(() => {\n const e = error()\n return e instanceof JsonApiError ? e : undefined\n }),\n value: value.asReadonly(),\n reset: () => {\n generation++\n value.set(undefined)\n error.set(undefined)\n pending.set(0)\n },\n }\n}\n","import type { Collection, Page, PageParamsOf, PaginatorRole } from '@haddowg/json-api-client'\n\n/**\n * The `page` query a link selects, typed by the `$page` it came from: the strategy's members under\n * their wire keys (counts as numbers, cursors as strings), plus the menu arm the read picked.\n */\nexport type PageParams<P extends Page = Page> = PageParamsOf<P>\n\n/**\n * The `page` params for the page after `list`, read from its `next` link, or `undefined` on the\n * last page (or before anything has loaded). Works the same for every strategy, because the\n * server's link already carries the right parameters. Feed the result back into the signal your\n * resource's params read:\n *\n * ```ts\n * const next = nextPage(this.albums.value())\n * if (next) this.page.set(next)\n * ```\n */\nexport function nextPage<P extends Page>(\n list: { readonly $page: P } | undefined,\n): PageParams<P> | undefined {\n return pageParams(list?.$page, list?.$page.links.next)\n}\n\n/** The `page` params for the page before `list`, from its `prev` link. See {@link nextPage}. */\nexport function prevPage<P extends Page>(\n list: { readonly $page: P } | undefined,\n): PageParams<P> | undefined {\n return pageParams(list?.$page, list?.$page.links.prev)\n}\n\nconst CURSOR_ROLES: readonly string[] = ['after', 'before']\n\nfunction pageParams<P extends Page>(\n page: P | undefined,\n link: string | undefined,\n): PageParams<P> | undefined {\n if (page === undefined || link === undefined) {\n return undefined\n }\n const keys = Object.entries(page.keys) as [PaginatorRole, string][]\n const query = link.includes('?') ? link.slice(link.indexOf('?') + 1).split('#')[0] : ''\n const params: Record<string, string | number> = {}\n for (const [name, value] of new URLSearchParams(query)) {\n const member = /^page\\[([^\\]]+)\\]$/.exec(name)?.[1]\n if (member === undefined) {\n continue\n }\n const role = keys.find(([, key]) => key === member)?.[0]\n const count = Number(value)\n params[member] =\n role !== undefined && !CURSOR_ROLES.includes(role) && value !== '' && Number.isFinite(count)\n ? count\n : value\n }\n return params as PageParams<P>\n}\n\n/** Options for {@link loadAll}. */\nexport interface LoadAllOptions {\n /** Stops the walk, rejecting with the signal's reason. Pass the loader's `abortSignal`. */\n signal?: AbortSignal\n /** Stop after this many pages, as a guard against an unexpectedly large collection. */\n maxPages?: number\n}\n\n/**\n * Follow `next` links from the first page until the last, and return every member in one plain\n * array. Use it inside a loader to read a whole (small) collection:\n *\n * ```ts\n * loader: ({ client, abortSignal }) =>\n * loadAll(client.genres.list({ signal: abortSignal }), { signal: abortSignal })\n * ```\n */\nexport async function loadAll<T>(\n first: Collection<T> | PromiseLike<Collection<T>>,\n options: LoadAllOptions = {},\n): Promise<T[]> {\n const { signal, maxPages = Number.POSITIVE_INFINITY } = options\n let page: Collection<T> | undefined = await first\n const all: T[] = []\n let count = 0\n while (page !== undefined && count < maxPages) {\n all.push(...page)\n count++\n if (count >= maxPages) {\n break\n }\n signal?.throwIfAborted()\n page = await page.$next(signal !== undefined ? { signal } : undefined)\n }\n return all\n}\n","// The only module that touches Angular's `resource()`. It is experimental before Angular 22, so a\n// change to its options or ref shape is absorbed here.\nimport {\n assertInInjectionContext,\n computed,\n inject,\n type InjectionToken,\n Injector,\n resource,\n type ResourceRef,\n type ResourceStatus,\n type Signal,\n type ValueEqualityFn,\n} from '@angular/core'\nimport { type ErrorCodeOf, JsonApiError, type StructuralGuardError } from '@haddowg/json-api-client'\nimport { JSON_API_CLIENT, type RegisteredClient } from './provide'\n\n/**\n * What a read can fail with: a `JsonApiError` for a non-2xx response, a `StructuralGuardError`\n * for a response that isn't a JSON:API document, or any other error (a network failure, a\n * validator's error). Narrow with `instanceof`. `E` types the `JsonApiError`'s codes.\n */\nexport type JsonApiResourceError<E extends string = string> =\n | JsonApiError<E>\n | StructuralGuardError\n | Error\n\n/** What the loader receives each time the params change or the resource reloads. */\nexport interface JsonApiLoaderContext<C, P> {\n client: C\n params: P\n /** Aborts when the params change again or the resource is destroyed. Pass it to the read. */\n abortSignal: AbortSignal\n previous: { status: ResourceStatus }\n}\n\n/** Options for {@link jsonApiResource}. */\nexport interface JsonApiResourceOptions<T, P, C> {\n /**\n * A reactive function computing the read's params from signals. Return `undefined` while the\n * read isn't ready (no id yet, say) and the resource stays idle. Leave it out to load once.\n */\n params?: () => P | undefined\n /** Make the read. Return the client call's promise, passing `abortSignal` as its `signal`. */\n loader: (context: JsonApiLoaderContext<C, P>) => PromiseLike<T>\n /** The client to read with. Defaults to the one `provideJsonApiClient` registered without a token. */\n client?: InjectionToken<C>\n /** The value while nothing has loaded yet. */\n defaultValue?: T\n equal?: ValueEqualityFn<T>\n /** Create the resource outside an injection context. */\n injector?: Injector\n debugName?: string\n}\n\n/**\n * Angular's `ResourceRef` with `error()` typed to what a JSON:API read throws, plus\n * `jsonApiError()`, the error when it is a `JsonApiError` (handy in templates, which can't\n * use `instanceof`). `E` is the client's error-code union, so a generated client's\n * `jsonApiError()` carries its server's `ErrorCode`.\n */\nexport interface JsonApiResourceRef<T, E extends string = string> extends Omit<\n ResourceRef<T>,\n 'error' | 'hasValue'\n> {\n readonly error: Signal<JsonApiResourceError<E> | undefined>\n readonly jsonApiError: Signal<JsonApiError<E> | undefined>\n /** Whether a value is loaded. Narrows `value()` to exclude `undefined`, in templates too. */\n hasValue(\n this: T extends undefined ? this : never,\n ): this is JsonApiResourceRef<Exclude<T, undefined>, E>\n hasValue(): boolean\n}\n\n/**\n * A signal-based JSON:API read: a thin wrapper over Angular's `resource()` that injects the\n * client and hands it to the loader. Changing a signal the params read starts a new load and\n * aborts the one in flight.\n *\n * ```ts\n * readonly albums = jsonApiResource({\n * params: () => ({ filter: { title: this.search() } }),\n * loader: ({ client, params, abortSignal }) =>\n * client.albums.list({ ...params, include: ['artist'], signal: abortSignal }),\n * })\n * ```\n */\nexport function jsonApiResource<T, P = unknown, C = RegisteredClient>(\n options: JsonApiResourceOptions<T, P, C> & { defaultValue: T },\n): JsonApiResourceRef<T, ErrorCodeOf<C>>\nexport function jsonApiResource<T, P = unknown, C = RegisteredClient>(\n options: JsonApiResourceOptions<T, P, C>,\n): JsonApiResourceRef<T | undefined, ErrorCodeOf<C>>\nexport function jsonApiResource<T, P, C>(\n options: JsonApiResourceOptions<T, P, C>,\n): JsonApiResourceRef<T | undefined, ErrorCodeOf<C>> {\n if (options.injector === undefined) {\n assertInInjectionContext(jsonApiResource)\n }\n const injector = options.injector ?? inject(Injector)\n const client = injector.get(options.client ?? (JSON_API_CLIENT as InjectionToken<C>))\n\n const ref = resource<T | undefined, P | undefined>({\n ...(options.params !== undefined ? { params: options.params } : {}),\n loader: ({ params, abortSignal, previous }) =>\n options.loader({ client, params: params as P, abortSignal, previous }),\n ...(options.defaultValue !== undefined ? { defaultValue: options.defaultValue } : {}),\n ...(options.equal !== undefined\n ? { equal: options.equal as ValueEqualityFn<T | undefined> }\n : {}),\n ...(options.debugName !== undefined ? { debugName: options.debugName } : {}),\n injector,\n })\n\n const jsonApiError = computed(() => {\n const error = ref.error()\n return error instanceof JsonApiError ? error : undefined\n })\n Object.defineProperty(ref, 'jsonApiError', { value: jsonApiError, enumerable: true })\n return ref as unknown as JsonApiResourceRef<T | undefined, ErrorCodeOf<C>>\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haddowg/json-api-angular",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Signal-based Angular bindings for the JSON:API client: an HttpClient transport, DI providers, and resource/mutation helpers.",
5
5
  "license": "MIT",
6
6
  "author": "Gregory Haddow (https://github.com/haddowg)",
@@ -48,7 +48,7 @@
48
48
  "provenance": true
49
49
  },
50
50
  "dependencies": {
51
- "@haddowg/json-api-client": "^1.1.0"
51
+ "@haddowg/json-api-client": "^1.3.0"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@angular/common": "^20.0.0 || ^21.0.0 || ^22.0.0",