@furystack/rest 9.0.0 → 10.0.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/CHANGELOG.md +17 -0
- package/esm/api-endpoint-schema.d.ts +10 -32
- package/esm/api-endpoint-schema.d.ts.map +1 -1
- package/esm/deserialize-query-string.d.ts +10 -5
- package/esm/deserialize-query-string.d.ts.map +1 -1
- package/esm/deserialize-query-string.js +7 -4
- package/esm/deserialize-query-string.js.map +1 -1
- package/esm/endpoint-models/delete-endpoint.d.ts +1 -3
- package/esm/endpoint-models/delete-endpoint.d.ts.map +1 -1
- package/esm/endpoint-models/get-collection-endpoint.d.ts +3 -10
- package/esm/endpoint-models/get-collection-endpoint.d.ts.map +1 -1
- package/esm/endpoint-models/get-endpoint.d.ts +1 -9
- package/esm/endpoint-models/get-endpoint.d.ts.map +1 -1
- package/esm/endpoint-models/patch-endpoint.d.ts +1 -3
- package/esm/endpoint-models/patch-endpoint.d.ts.map +1 -1
- package/esm/endpoint-models/post-endpoint.d.ts +1 -3
- package/esm/endpoint-models/post-endpoint.d.ts.map +1 -1
- package/esm/request-error.d.ts +6 -0
- package/esm/request-error.d.ts.map +1 -1
- package/esm/request-error.js +6 -0
- package/esm/request-error.js.map +1 -1
- package/esm/serialize-to-query-string.d.ts +14 -8
- package/esm/serialize-to-query-string.d.ts.map +1 -1
- package/esm/serialize-to-query-string.js +14 -8
- package/esm/serialize-to-query-string.js.map +1 -1
- package/package.json +5 -5
- package/src/api-endpoint-schema.ts +10 -32
- package/src/deserialize-query-string.ts +8 -5
- package/src/endpoint-models/delete-endpoint.ts +1 -3
- package/src/endpoint-models/get-collection-endpoint.ts +3 -10
- package/src/endpoint-models/get-endpoint.ts +3 -15
- package/src/endpoint-models/patch-endpoint.ts +1 -3
- package/src/endpoint-models/post-endpoint.ts +1 -3
- package/src/request-error.ts +6 -0
- package/src/serialize-to-query-string.ts +14 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [10.0.1] - 2026-05-21
|
|
4
|
+
|
|
5
|
+
### 📚 Documentation
|
|
6
|
+
|
|
7
|
+
- Rewrote JSDoc on the endpoint models (`GetEndpoint`, `GetCollectionEndpoint`, `PostEndpoint`, `PatchEndpoint`, `DeleteEndpoint`), `ApiEndpointSchema`, `RequestError`, and the (de)serialize helpers to follow the new value-test guidance: dropped restate-the-type narration, kept intent / trade-offs / constraints (e.g. how `Validate()` and `Authenticate()` populate the schema metadata).
|
|
8
|
+
|
|
9
|
+
### ⬆️ Dependencies
|
|
10
|
+
|
|
11
|
+
- Bump dev `vitest` to `^4.1.5`.
|
|
12
|
+
- Bumped `@types/node` to `^25.9.1` and `vitest` to `^4.1.7`. No source changes — dev-tooling bump only.
|
|
13
|
+
|
|
14
|
+
## [10.0.0] - 2026-04-25
|
|
15
|
+
|
|
16
|
+
### 💥 Breaking Changes
|
|
17
|
+
|
|
18
|
+
- Version bumped to align with the monorepo-wide functional DI migration. No DI surface in this package; it ships REST API type definitions and contracts only. See the [v7 migration guide](../../docs/migrations/v7-functional-di.md) and the `@furystack/rest-service` changelog for the server-side impact.
|
|
19
|
+
|
|
3
20
|
## [9.0.0] - 2026-04-22
|
|
4
21
|
|
|
5
22
|
### 💥 Breaking Changes
|
|
@@ -12,49 +12,27 @@ import type { RestApi } from './rest-api.js';
|
|
|
12
12
|
*/
|
|
13
13
|
export type Schema = unknown;
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* Reflection metadata for a single endpoint. Populated by the wrappers
|
|
16
|
+
* applied at definition time (`Validate()`, `Authenticate()`) and read by
|
|
17
|
+
* `generateOpenApiDocument` and the `/schema` action. The HTTP method is
|
|
18
|
+
* implicit in the parent {@link ApiEndpointSchema} structure key.
|
|
17
19
|
*/
|
|
18
20
|
export type ApiEndpointDefinition = {
|
|
19
|
-
/**
|
|
20
|
-
* The path of the endpoint, which is the URL pattern that the endpoint responds to.
|
|
21
|
-
*/
|
|
22
21
|
path: string;
|
|
23
|
-
/**
|
|
24
|
-
* The JSON schema that defines the structure of the request and response for this endpoint.
|
|
25
|
-
* To include a schema, wrap your endpoint with a Validate({...}) call during the implementation.
|
|
26
|
-
*/
|
|
22
|
+
/** Populated by `Validate({ schema })`. Empty string when missing. */
|
|
27
23
|
schema: Schema;
|
|
28
|
-
/**
|
|
29
|
-
* The name of the schema, which can be used to reference this schema in documentation or other contexts.
|
|
30
|
-
* To include a schema name, wrap your endpoint with a Validate({...}) call during the implementation.
|
|
31
|
-
*/
|
|
24
|
+
/** Populated by `Validate({ schemaName })`. Empty string when missing. */
|
|
32
25
|
schemaName: string;
|
|
33
|
-
/**
|
|
34
|
-
* Indicates whether the endpoint requires authentication.
|
|
35
|
-
* To include the flag, wrap your endpoint with an Authenticate() call during the implementation.
|
|
36
|
-
*/
|
|
26
|
+
/** Populated by `Authenticate()` wrapping the action. */
|
|
37
27
|
isAuthenticated: boolean;
|
|
38
|
-
/**
|
|
39
|
-
* Tags for API documentation grouping.
|
|
40
|
-
*/
|
|
41
28
|
tags?: string[];
|
|
42
|
-
/**
|
|
43
|
-
* Marks the endpoint as deprecated.
|
|
44
|
-
*/
|
|
45
29
|
deprecated?: boolean;
|
|
46
|
-
/**
|
|
47
|
-
* A short summary of the endpoint.
|
|
48
|
-
*/
|
|
49
30
|
summary?: string;
|
|
50
|
-
/**
|
|
51
|
-
* A longer description of the endpoint.
|
|
52
|
-
*/
|
|
53
31
|
description?: string;
|
|
54
32
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
33
|
+
* OpenAPI security scheme names required for this endpoint. Populated by
|
|
34
|
+
* `Authorize()` and friends. When present overrides the
|
|
35
|
+
* `isAuthenticated` boolean fallback in `generateOpenApiDocument`.
|
|
58
36
|
*/
|
|
59
37
|
securitySchemes?: string[];
|
|
60
38
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-endpoint-schema.d.ts","sourceRoot":"","sources":["../src/api-endpoint-schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,KAAK,EACV,aAAa,EACb,2BAA2B,EAC3B,aAAa,EACb,oBAAoB,EACpB,YAAY,EACZ,SAAS,EACV,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAE5C;;;;;;;;GAQG;AACH,MAAM,MAAM,MAAM,GAAG,OAAO,CAAA;AAE5B
|
|
1
|
+
{"version":3,"file":"api-endpoint-schema.d.ts","sourceRoot":"","sources":["../src/api-endpoint-schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,KAAK,EACV,aAAa,EACb,2BAA2B,EAC3B,aAAa,EACb,oBAAoB,EACpB,YAAY,EACZ,SAAS,EACV,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAE5C;;;;;;;;GAQG;AACH,MAAM,MAAM,MAAM,GAAG,OAAO,CAAA;AAE5B;;;;;GAKG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAA;IACd,0EAA0E;IAC1E,UAAU,EAAE,MAAM,CAAA;IAClB,yDAAyD;IACzD,eAAe,EAAE,OAAO,CAAA;IACxB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,OAAO,CAAC,EAAE,YAAY,EAAE,CAAA;IACxB,IAAI,CAAC,EAAE,SAAS,EAAE,CAAA;IAClB,YAAY,CAAC,EAAE,2BAA2B,CAAA;IAC1C,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAA;CACvD,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,iBAAiB,CAAC,IAAI,SAAS,OAAO,GAAG,OAAO,IAAI;IAC9D,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,mBAAmB,CAAA;IAC9B,SAAS,EAAE;SACR,OAAO,IAAI,MAAM,CAAC,CAAC,EAAE,OAAO,SAAS,MAAM,IAAI,GAC5C,IAAI,CAAC,OAAO,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3C;aAAG,KAAK,IAAI,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,MAAM,GAAG,qBAAqB;SAAE,GAClE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,GACvC,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC;KAC1C,CAAA;CACF,CAAA"}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* @returns The decoded value
|
|
2
|
+
* Reverses the encoding produced by `serializeValue`. Throws a 400
|
|
3
|
+
* {@link RequestError} on malformed input — surfaces to clients as a
|
|
4
|
+
* "bad request" rather than crashing the server.
|
|
6
5
|
*/
|
|
7
6
|
export declare const decode: <T>(value: string) => T;
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Decodes a query string into a typed object. Empty keys/values are
|
|
9
|
+
* skipped. Each surviving value is deserialized via {@link decode}.
|
|
10
|
+
*/
|
|
11
|
+
export declare const deserializeQueryString: (fullQueryString: string) => {
|
|
12
|
+
[k: string]: unknown;
|
|
13
|
+
};
|
|
9
14
|
//# sourceMappingURL=deserialize-query-string.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deserialize-query-string.d.ts","sourceRoot":"","sources":["../src/deserialize-query-string.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"deserialize-query-string.d.ts","sourceRoot":"","sources":["../src/deserialize-query-string.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,eAAO,MAAM,MAAM,GAAI,CAAC,EAAE,OAAO,MAAM,KAAG,CAMzC,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GAAI,iBAAiB,MAAM;;CAM7D,CAAA"}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { RequestError } from './request-error.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* @returns The decoded value
|
|
3
|
+
* Reverses the encoding produced by `serializeValue`. Throws a 400
|
|
4
|
+
* {@link RequestError} on malformed input — surfaces to clients as a
|
|
5
|
+
* "bad request" rather than crashing the server.
|
|
7
6
|
*/
|
|
8
7
|
export const decode = (value) => {
|
|
9
8
|
try {
|
|
@@ -13,6 +12,10 @@ export const decode = (value) => {
|
|
|
13
12
|
throw new RequestError('Failed to decode query parameter value', 400);
|
|
14
13
|
}
|
|
15
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* Decodes a query string into a typed object. Empty keys/values are
|
|
17
|
+
* skipped. Each surviving value is deserialized via {@link decode}.
|
|
18
|
+
*/
|
|
16
19
|
export const deserializeQueryString = (fullQueryString) => {
|
|
17
20
|
const params = [...new URLSearchParams(fullQueryString).entries()]
|
|
18
21
|
.filter(([key, value]) => key && value)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deserialize-query-string.js","sourceRoot":"","sources":["../src/deserialize-query-string.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD
|
|
1
|
+
{"version":3,"file":"deserialize-query-string.js","sourceRoot":"","sources":["../src/deserialize-query-string.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD;;;;GAIG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAI,KAAa,EAAK,EAAE;IAC5C,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAM,CAAA;IACrF,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,YAAY,CAAC,wCAAwC,EAAE,GAAG,CAAC,CAAA;IACvE,CAAC;AACH,CAAC,CAAA;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,eAAuB,EAAE,EAAE;IAChE,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,eAAe,CAAC,eAAe,CAAC,CAAC,OAAO,EAAE,CAAC;SAC/D,MAAM,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,KAAK,CAAC;SACtC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAU,CAAC,CAAA;IAEvD,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;AACnC,CAAC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delete-endpoint.d.ts","sourceRoot":"","sources":["../../src/endpoint-models/delete-endpoint.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"delete-endpoint.d.ts","sourceRoot":"","sources":["../../src/endpoint-models/delete-endpoint.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,MAAM,MAAM,cAAc,CAAC,CAAC,EAAE,WAAW,SAAS,MAAM,CAAC,IAAI;IAC3D,GAAG,EAAE;QAAE,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,CAAA;KAAE,CAAA;IAC3B,MAAM,EAAE,MAAM,CAAA;CACf,CAAA"}
|
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
import type { FindOptions } from '@furystack/core';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Collection result. `count` is the **total** matching entity count
|
|
4
|
+
* (ignores `top` / `skip`); `entries` is the page returned for this query.
|
|
4
5
|
*/
|
|
5
6
|
export type GetCollectionResult<T> = {
|
|
6
|
-
/**
|
|
7
|
-
* The Total count of entities
|
|
8
|
-
*/
|
|
9
7
|
count: number;
|
|
10
|
-
/**
|
|
11
|
-
* List of the selected entities
|
|
12
|
-
*/
|
|
13
8
|
entries: T[];
|
|
14
9
|
};
|
|
15
|
-
/**
|
|
16
|
-
* Rest endpoint model for getting / querying collections
|
|
17
|
-
*/
|
|
10
|
+
/** GET-collection endpoint contract. */
|
|
18
11
|
export type GetCollectionEndpoint<T> = {
|
|
19
12
|
query: {
|
|
20
13
|
findOptions?: FindOptions<T, Array<keyof T>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-collection-endpoint.d.ts","sourceRoot":"","sources":["../../src/endpoint-models/get-collection-endpoint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAElD
|
|
1
|
+
{"version":3,"file":"get-collection-endpoint.d.ts","sourceRoot":"","sources":["../../src/endpoint-models/get-collection-endpoint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAElD;;;GAGG;AACH,MAAM,MAAM,mBAAmB,CAAC,CAAC,IAAI;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,CAAC,EAAE,CAAA;CACb,CAAA;AAED,wCAAwC;AACxC,MAAM,MAAM,qBAAqB,CAAC,CAAC,IAAI;IACrC,KAAK,EAAE;QAAE,WAAW,CAAC,EAAE,WAAW,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;KAAE,CAAA;IACvD,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAA;CAC/B,CAAA"}
|
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Endpoint model for getting a single entity
|
|
3
|
-
*/
|
|
1
|
+
/** GET-by-id endpoint contract. `select` narrows the projected fields. */
|
|
4
2
|
export type GetEntityEndpoint<T, TPrimaryKey extends keyof T> = {
|
|
5
3
|
query: {
|
|
6
|
-
/**
|
|
7
|
-
* The list of fields to select
|
|
8
|
-
*/
|
|
9
4
|
select?: Array<keyof T>;
|
|
10
5
|
};
|
|
11
6
|
url: {
|
|
12
|
-
/**
|
|
13
|
-
* The entity's unique identifier
|
|
14
|
-
*/
|
|
15
7
|
id: T[TPrimaryKey];
|
|
16
8
|
};
|
|
17
9
|
result: T;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-endpoint.d.ts","sourceRoot":"","sources":["../../src/endpoint-models/get-endpoint.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"get-endpoint.d.ts","sourceRoot":"","sources":["../../src/endpoint-models/get-endpoint.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,MAAM,MAAM,iBAAiB,CAAC,CAAC,EAAE,WAAW,SAAS,MAAM,CAAC,IAAI;IAC9D,KAAK,EAAE;QAAE,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAA;KAAE,CAAA;IAClC,GAAG,EAAE;QAAE,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,CAAA;KAAE,CAAA;IAC3B,MAAM,EAAE,CAAC,CAAA;CACV,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"patch-endpoint.d.ts","sourceRoot":"","sources":["../../src/endpoint-models/patch-endpoint.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"patch-endpoint.d.ts","sourceRoot":"","sources":["../../src/endpoint-models/patch-endpoint.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,MAAM,MAAM,aAAa,CAAC,CAAC,EAAE,WAAW,SAAS,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI;IACtF,IAAI,EAAE,aAAa,CAAA;IACnB,GAAG,EAAE;QAAE,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,CAAA;KAAE,CAAA;IAC3B,MAAM,EAAE,MAAM,CAAA;CACf,CAAA"}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { WithOptionalId } from '@furystack/core';
|
|
2
|
-
/**
|
|
3
|
-
* Endpoint model for creating new entities
|
|
4
|
-
*/
|
|
2
|
+
/** POST endpoint contract. `TPostData` defaults to `T` with the primary key optional. */
|
|
5
3
|
export type PostEndpoint<T, TPrimaryKey extends keyof T, TPostData = WithOptionalId<T, TPrimaryKey>> = {
|
|
6
4
|
body: TPostData;
|
|
7
5
|
result: T;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post-endpoint.d.ts","sourceRoot":"","sources":["../../src/endpoint-models/post-endpoint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD
|
|
1
|
+
{"version":3,"file":"post-endpoint.d.ts","sourceRoot":"","sources":["../../src/endpoint-models/post-endpoint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD,yFAAyF;AACzF,MAAM,MAAM,YAAY,CAAC,CAAC,EAAE,WAAW,SAAS,MAAM,CAAC,EAAE,SAAS,GAAG,cAAc,CAAC,CAAC,EAAE,WAAW,CAAC,IAAI;IACrG,IAAI,EAAE,SAAS,CAAA;IACf,MAAM,EAAE,CAAC,CAAA;CACV,CAAA"}
|
package/esm/request-error.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throw inside a `RequestAction` to short-circuit with a specific HTTP
|
|
3
|
+
* status. `ErrorAction` translates this to `JsonResult({ message }, responseCode)`.
|
|
4
|
+
* Prefer this over plain `Error` (`furystack/rest-action-use-request-error`
|
|
5
|
+
* enforces this).
|
|
6
|
+
*/
|
|
1
7
|
export declare class RequestError extends Error {
|
|
2
8
|
readonly responseCode: number;
|
|
3
9
|
constructor(msg: string, responseCode: number);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-error.d.ts","sourceRoot":"","sources":["../src/request-error.ts"],"names":[],"mappings":"AAAA,qBAAa,YAAa,SAAQ,KAAK;aAGnB,YAAY,EAAE,MAAM;gBADpC,GAAG,EAAE,MAAM,EACK,YAAY,EAAE,MAAM;CAIvC"}
|
|
1
|
+
{"version":3,"file":"request-error.d.ts","sourceRoot":"","sources":["../src/request-error.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,qBAAa,YAAa,SAAQ,KAAK;aAGnB,YAAY,EAAE,MAAM;gBADpC,GAAG,EAAE,MAAM,EACK,YAAY,EAAE,MAAM;CAIvC"}
|
package/esm/request-error.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throw inside a `RequestAction` to short-circuit with a specific HTTP
|
|
3
|
+
* status. `ErrorAction` translates this to `JsonResult({ message }, responseCode)`.
|
|
4
|
+
* Prefer this over plain `Error` (`furystack/rest-action-use-request-error`
|
|
5
|
+
* enforces this).
|
|
6
|
+
*/
|
|
1
7
|
export class RequestError extends Error {
|
|
2
8
|
responseCode;
|
|
3
9
|
constructor(msg, responseCode) {
|
package/esm/request-error.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-error.js","sourceRoot":"","sources":["../src/request-error.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,YAAa,SAAQ,KAAK;IAGnB;IAFlB,YACE,GAAW,EACK,YAAoB;QAEpC,KAAK,CAAC,GAAG,CAAC,CAAA;QAFM,iBAAY,GAAZ,YAAY,CAAQ;IAGtC,CAAC;CACF"}
|
|
1
|
+
{"version":3,"file":"request-error.js","sourceRoot":"","sources":["../src/request-error.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,OAAO,YAAa,SAAQ,KAAK;IAGnB;IAFlB,YACE,GAAW,EACK,YAAoB;QAEpC,KAAK,CAAC,GAAG,CAAC,CAAA;QAFM,iBAAY,GAAZ,YAAY,CAAQ;IAGtC,CAAC;CACF"}
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
2
|
+
* Serializes any JSON-stringifiable value to a URL-safe base64 string
|
|
3
|
+
* suitable for `application/x-www-form-urlencoded`. Pipeline:
|
|
4
|
+
*
|
|
5
|
+
* 1. `JSON.stringify` (preserves type info even for primitives)
|
|
6
|
+
* 2. `encodeURIComponent` (UTF-8 → percent-encoded ASCII so `btoa` works)
|
|
7
|
+
* 3. `unescape` (drop the percent encoding before base64)
|
|
8
|
+
* 4. `btoa` (base64 — keeps the result short)
|
|
9
|
+
* 5. `encodeURIComponent` (URL-safe `=` → `%3D`)
|
|
10
|
+
*
|
|
11
|
+
* Reversed by `decode` in `deserialize-query-string.ts`.
|
|
10
12
|
*/
|
|
11
13
|
export declare const serializeValue: (value: any) => string;
|
|
14
|
+
/**
|
|
15
|
+
* Encodes an object as a query string. `undefined` values are skipped;
|
|
16
|
+
* every other value is run through {@link serializeValue}.
|
|
17
|
+
*/
|
|
12
18
|
export declare const serializeToQueryString: <T extends object>(queryObject: T) => string;
|
|
13
19
|
//# sourceMappingURL=serialize-to-query-string.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serialize-to-query-string.d.ts","sourceRoot":"","sources":["../src/serialize-to-query-string.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"serialize-to-query-string.d.ts","sourceRoot":"","sources":["../src/serialize-to-query-string.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,GAAG,WACsC,CAAA;AAE/E;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GAAI,CAAC,SAAS,MAAM,EAAE,aAAa,CAAC,KAAG,MAQzE,CAAA"}
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
2
|
+
* Serializes any JSON-stringifiable value to a URL-safe base64 string
|
|
3
|
+
* suitable for `application/x-www-form-urlencoded`. Pipeline:
|
|
4
|
+
*
|
|
5
|
+
* 1. `JSON.stringify` (preserves type info even for primitives)
|
|
6
|
+
* 2. `encodeURIComponent` (UTF-8 → percent-encoded ASCII so `btoa` works)
|
|
7
|
+
* 3. `unescape` (drop the percent encoding before base64)
|
|
8
|
+
* 4. `btoa` (base64 — keeps the result short)
|
|
9
|
+
* 5. `encodeURIComponent` (URL-safe `=` → `%3D`)
|
|
10
|
+
*
|
|
11
|
+
* Reversed by `decode` in `deserialize-query-string.ts`.
|
|
10
12
|
*/
|
|
11
13
|
export const serializeValue = (value) => encodeURIComponent(btoa(unescape(encodeURIComponent(JSON.stringify(value)))));
|
|
14
|
+
/**
|
|
15
|
+
* Encodes an object as a query string. `undefined` values are skipped;
|
|
16
|
+
* every other value is run through {@link serializeValue}.
|
|
17
|
+
*/
|
|
12
18
|
export const serializeToQueryString = (queryObject) => {
|
|
13
19
|
return new URLSearchParams(Object.fromEntries(Object.entries(queryObject)
|
|
14
20
|
.filter(([, value]) => value !== undefined)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serialize-to-query-string.js","sourceRoot":"","sources":["../src/serialize-to-query-string.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"serialize-to-query-string.js","sourceRoot":"","sources":["../src/serialize-to-query-string.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAU,EAAE,EAAE,CAC3C,kBAAkB,CAAC,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAE/E;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAmB,WAAc,EAAU,EAAE;IACjF,OAAO,IAAI,eAAe,CACxB,MAAM,CAAC,WAAW,CAChB,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC;SACxB,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC;SAC1C,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CACvD,CACF,CAAC,QAAQ,EAAE,CAAA;AACd,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@furystack/rest",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.1",
|
|
4
4
|
"description": "Generic REST package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -38,13 +38,13 @@
|
|
|
38
38
|
},
|
|
39
39
|
"homepage": "https://github.com/furystack/furystack",
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@furystack/core": "^
|
|
42
|
-
"@furystack/inject": "^
|
|
41
|
+
"@furystack/core": "^17.1.0",
|
|
42
|
+
"@furystack/inject": "^13.0.1"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@types/node": "^25.
|
|
45
|
+
"@types/node": "^25.9.1",
|
|
46
46
|
"typescript": "^6.0.3",
|
|
47
|
-
"vitest": "^4.1.
|
|
47
|
+
"vitest": "^4.1.7"
|
|
48
48
|
},
|
|
49
49
|
"engines": {
|
|
50
50
|
"node": ">=22.0.0"
|
|
@@ -21,49 +21,27 @@ import type { RestApi } from './rest-api.js'
|
|
|
21
21
|
export type Schema = unknown
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* Reflection metadata for a single endpoint. Populated by the wrappers
|
|
25
|
+
* applied at definition time (`Validate()`, `Authenticate()`) and read by
|
|
26
|
+
* `generateOpenApiDocument` and the `/schema` action. The HTTP method is
|
|
27
|
+
* implicit in the parent {@link ApiEndpointSchema} structure key.
|
|
26
28
|
*/
|
|
27
29
|
export type ApiEndpointDefinition = {
|
|
28
|
-
/**
|
|
29
|
-
* The path of the endpoint, which is the URL pattern that the endpoint responds to.
|
|
30
|
-
*/
|
|
31
30
|
path: string
|
|
32
|
-
/**
|
|
33
|
-
* The JSON schema that defines the structure of the request and response for this endpoint.
|
|
34
|
-
* To include a schema, wrap your endpoint with a Validate({...}) call during the implementation.
|
|
35
|
-
*/
|
|
31
|
+
/** Populated by `Validate({ schema })`. Empty string when missing. */
|
|
36
32
|
schema: Schema
|
|
37
|
-
/**
|
|
38
|
-
* The name of the schema, which can be used to reference this schema in documentation or other contexts.
|
|
39
|
-
* To include a schema name, wrap your endpoint with a Validate({...}) call during the implementation.
|
|
40
|
-
*/
|
|
33
|
+
/** Populated by `Validate({ schemaName })`. Empty string when missing. */
|
|
41
34
|
schemaName: string
|
|
42
|
-
/**
|
|
43
|
-
* Indicates whether the endpoint requires authentication.
|
|
44
|
-
* To include the flag, wrap your endpoint with an Authenticate() call during the implementation.
|
|
45
|
-
*/
|
|
35
|
+
/** Populated by `Authenticate()` wrapping the action. */
|
|
46
36
|
isAuthenticated: boolean
|
|
47
|
-
/**
|
|
48
|
-
* Tags for API documentation grouping.
|
|
49
|
-
*/
|
|
50
37
|
tags?: string[]
|
|
51
|
-
/**
|
|
52
|
-
* Marks the endpoint as deprecated.
|
|
53
|
-
*/
|
|
54
38
|
deprecated?: boolean
|
|
55
|
-
/**
|
|
56
|
-
* A short summary of the endpoint.
|
|
57
|
-
*/
|
|
58
39
|
summary?: string
|
|
59
|
-
/**
|
|
60
|
-
* A longer description of the endpoint.
|
|
61
|
-
*/
|
|
62
40
|
description?: string
|
|
63
41
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
42
|
+
* OpenAPI security scheme names required for this endpoint. Populated by
|
|
43
|
+
* `Authorize()` and friends. When present overrides the
|
|
44
|
+
* `isAuthenticated` boolean fallback in `generateOpenApiDocument`.
|
|
67
45
|
*/
|
|
68
46
|
securitySchemes?: string[]
|
|
69
47
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { RequestError } from './request-error.js'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* @returns The decoded value
|
|
4
|
+
* Reverses the encoding produced by `serializeValue`. Throws a 400
|
|
5
|
+
* {@link RequestError} on malformed input — surfaces to clients as a
|
|
6
|
+
* "bad request" rather than crashing the server.
|
|
8
7
|
*/
|
|
9
8
|
export const decode = <T>(value: string): T => {
|
|
10
9
|
try {
|
|
@@ -14,10 +13,14 @@ export const decode = <T>(value: string): T => {
|
|
|
14
13
|
}
|
|
15
14
|
}
|
|
16
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Decodes a query string into a typed object. Empty keys/values are
|
|
18
|
+
* skipped. Each surviving value is deserialized via {@link decode}.
|
|
19
|
+
*/
|
|
17
20
|
export const deserializeQueryString = (fullQueryString: string) => {
|
|
18
21
|
const params = [...new URLSearchParams(fullQueryString).entries()]
|
|
19
22
|
.filter(([key, value]) => key && value)
|
|
20
23
|
.map(([key, value]) => [key, decode(value)] as const)
|
|
21
24
|
|
|
22
|
-
return Object.fromEntries(params)
|
|
25
|
+
return Object.fromEntries(params)
|
|
23
26
|
}
|
|
@@ -1,22 +1,15 @@
|
|
|
1
1
|
import type { FindOptions } from '@furystack/core'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Collection result. `count` is the **total** matching entity count
|
|
5
|
+
* (ignores `top` / `skip`); `entries` is the page returned for this query.
|
|
5
6
|
*/
|
|
6
7
|
export type GetCollectionResult<T> = {
|
|
7
|
-
/**
|
|
8
|
-
* The Total count of entities
|
|
9
|
-
*/
|
|
10
8
|
count: number
|
|
11
|
-
/**
|
|
12
|
-
* List of the selected entities
|
|
13
|
-
*/
|
|
14
9
|
entries: T[]
|
|
15
10
|
}
|
|
16
11
|
|
|
17
|
-
/**
|
|
18
|
-
* Rest endpoint model for getting / querying collections
|
|
19
|
-
*/
|
|
12
|
+
/** GET-collection endpoint contract. */
|
|
20
13
|
export type GetCollectionEndpoint<T> = {
|
|
21
14
|
query: { findOptions?: FindOptions<T, Array<keyof T>> }
|
|
22
15
|
result: GetCollectionResult<T>
|
|
@@ -1,18 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Endpoint model for getting a single entity
|
|
3
|
-
*/
|
|
1
|
+
/** GET-by-id endpoint contract. `select` narrows the projected fields. */
|
|
4
2
|
export type GetEntityEndpoint<T, TPrimaryKey extends keyof T> = {
|
|
5
|
-
query: {
|
|
6
|
-
|
|
7
|
-
* The list of fields to select
|
|
8
|
-
*/
|
|
9
|
-
select?: Array<keyof T>
|
|
10
|
-
}
|
|
11
|
-
url: {
|
|
12
|
-
/**
|
|
13
|
-
* The entity's unique identifier
|
|
14
|
-
*/
|
|
15
|
-
id: T[TPrimaryKey]
|
|
16
|
-
}
|
|
3
|
+
query: { select?: Array<keyof T> }
|
|
4
|
+
url: { id: T[TPrimaryKey] }
|
|
17
5
|
result: T
|
|
18
6
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Endpoint model for updating entities
|
|
3
|
-
*/
|
|
1
|
+
/** PATCH-by-id endpoint contract. `TWritableData` defaults to `Partial<T>`. */
|
|
4
2
|
export type PatchEndpoint<T, TPrimaryKey extends keyof T, TWritableData = Partial<T>> = {
|
|
5
3
|
body: TWritableData
|
|
6
4
|
url: { id: T[TPrimaryKey] }
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { WithOptionalId } from '@furystack/core'
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Endpoint model for creating new entities
|
|
5
|
-
*/
|
|
3
|
+
/** POST endpoint contract. `TPostData` defaults to `T` with the primary key optional. */
|
|
6
4
|
export type PostEndpoint<T, TPrimaryKey extends keyof T, TPostData = WithOptionalId<T, TPrimaryKey>> = {
|
|
7
5
|
body: TPostData
|
|
8
6
|
result: T
|
package/src/request-error.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throw inside a `RequestAction` to short-circuit with a specific HTTP
|
|
3
|
+
* status. `ErrorAction` translates this to `JsonResult({ message }, responseCode)`.
|
|
4
|
+
* Prefer this over plain `Error` (`furystack/rest-action-use-request-error`
|
|
5
|
+
* enforces this).
|
|
6
|
+
*/
|
|
1
7
|
export class RequestError extends Error {
|
|
2
8
|
constructor(
|
|
3
9
|
msg: string,
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
2
|
+
* Serializes any JSON-stringifiable value to a URL-safe base64 string
|
|
3
|
+
* suitable for `application/x-www-form-urlencoded`. Pipeline:
|
|
4
|
+
*
|
|
5
|
+
* 1. `JSON.stringify` (preserves type info even for primitives)
|
|
6
|
+
* 2. `encodeURIComponent` (UTF-8 → percent-encoded ASCII so `btoa` works)
|
|
7
|
+
* 3. `unescape` (drop the percent encoding before base64)
|
|
8
|
+
* 4. `btoa` (base64 — keeps the result short)
|
|
9
|
+
* 5. `encodeURIComponent` (URL-safe `=` → `%3D`)
|
|
10
|
+
*
|
|
11
|
+
* Reversed by `decode` in `deserialize-query-string.ts`.
|
|
10
12
|
*/
|
|
11
13
|
export const serializeValue = (value: any) =>
|
|
12
14
|
encodeURIComponent(btoa(unescape(encodeURIComponent(JSON.stringify(value)))))
|
|
13
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Encodes an object as a query string. `undefined` values are skipped;
|
|
18
|
+
* every other value is run through {@link serializeValue}.
|
|
19
|
+
*/
|
|
14
20
|
export const serializeToQueryString = <T extends object>(queryObject: T): string => {
|
|
15
21
|
return new URLSearchParams(
|
|
16
22
|
Object.fromEntries(
|