@furystack/rest 10.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/esm/api-endpoint-schema.d.ts +10 -32
  3. package/esm/api-endpoint-schema.d.ts.map +1 -1
  4. package/esm/deserialize-query-string.d.ts +10 -5
  5. package/esm/deserialize-query-string.d.ts.map +1 -1
  6. package/esm/deserialize-query-string.js +7 -4
  7. package/esm/deserialize-query-string.js.map +1 -1
  8. package/esm/endpoint-models/delete-endpoint.d.ts +1 -3
  9. package/esm/endpoint-models/delete-endpoint.d.ts.map +1 -1
  10. package/esm/endpoint-models/get-collection-endpoint.d.ts +3 -10
  11. package/esm/endpoint-models/get-collection-endpoint.d.ts.map +1 -1
  12. package/esm/endpoint-models/get-endpoint.d.ts +1 -9
  13. package/esm/endpoint-models/get-endpoint.d.ts.map +1 -1
  14. package/esm/endpoint-models/patch-endpoint.d.ts +1 -3
  15. package/esm/endpoint-models/patch-endpoint.d.ts.map +1 -1
  16. package/esm/endpoint-models/post-endpoint.d.ts +1 -3
  17. package/esm/endpoint-models/post-endpoint.d.ts.map +1 -1
  18. package/esm/request-error.d.ts +6 -0
  19. package/esm/request-error.d.ts.map +1 -1
  20. package/esm/request-error.js +6 -0
  21. package/esm/request-error.js.map +1 -1
  22. package/esm/serialize-to-query-string.d.ts +14 -8
  23. package/esm/serialize-to-query-string.d.ts.map +1 -1
  24. package/esm/serialize-to-query-string.js +14 -8
  25. package/esm/serialize-to-query-string.js.map +1 -1
  26. package/package.json +5 -5
  27. package/src/api-endpoint-schema.ts +10 -32
  28. package/src/deserialize-query-string.ts +8 -5
  29. package/src/endpoint-models/delete-endpoint.ts +1 -3
  30. package/src/endpoint-models/get-collection-endpoint.ts +3 -10
  31. package/src/endpoint-models/get-endpoint.ts +3 -15
  32. package/src/endpoint-models/patch-endpoint.ts +1 -3
  33. package/src/endpoint-models/post-endpoint.ts +1 -3
  34. package/src/request-error.ts +6 -0
  35. package/src/serialize-to-query-string.ts +14 -8
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
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
+
3
14
  ## [10.0.0] - 2026-04-25
4
15
 
5
16
  ### 💥 Breaking Changes
@@ -12,49 +12,27 @@ import type { RestApi } from './rest-api.js';
12
12
  */
13
13
  export type Schema = unknown;
14
14
  /**
15
- * Represents the definition of an API endpoint, including its path, schema, and schema name.
16
- * The HTTP method is now implicit in the structure key.
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
- * The OpenAPI security scheme names required for this endpoint.
56
- * When present, used by `generateOpenApiDocument` to emit accurate per-operation `security`.
57
- * When absent, falls back to the `isAuthenticated` boolean behavior.
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;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC;;OAEG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,eAAe,EAAE,OAAO,CAAA;IACxB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;OAEG;IACH,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
+ {"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
- * Decodes a URL-safe base64 encoded JSON string back to its original value
3
- * Decoding steps: See the encoding steps in reverse order
4
- * @param value The value to decode
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
- export declare const deserializeQueryString: (fullQueryString: string) => Record<string, unknown>;
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;;;;;GAKG;AACH,eAAO,MAAM,MAAM,GAAI,CAAC,EAAE,OAAO,MAAM,KAAG,CAMzC,CAAA;AAED,eAAO,MAAM,sBAAsB,GAAI,iBAAiB,MAAM,KAKvB,MAAM,CAAC,MAAM,EAAE,OAAO,CAC5D,CAAA"}
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
- * Decodes a URL-safe base64 encoded JSON string back to its original value
4
- * Decoding steps: See the encoding steps in reverse order
5
- * @param value The value to decode
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;;;;;GAKG;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,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,CAA4B,CAAA;AAC9D,CAAC,CAAA"}
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,6 +1,4 @@
1
- /**
2
- * Endpoint model for deleting entities
3
- */
1
+ /** DELETE-by-id endpoint contract. */
4
2
  export type DeleteEndpoint<T, TPrimaryKey extends keyof T> = {
5
3
  url: {
6
4
  id: T[TPrimaryKey];
@@ -1 +1 @@
1
- {"version":3,"file":"delete-endpoint.d.ts","sourceRoot":"","sources":["../../src/endpoint-models/delete-endpoint.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,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
+ {"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
- * Response Model for GetCollection
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;;GAEG;AACH,MAAM,MAAM,mBAAmB,CAAC,CAAC,IAAI;IACnC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;OAEG;IACH,OAAO,EAAE,CAAC,EAAE,CAAA;CACb,CAAA;AAED;;GAEG;AACH,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
+ {"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;;GAEG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,EAAE,WAAW,SAAS,MAAM,CAAC,IAAI;IAC9D,KAAK,EAAE;QACL;;WAEG;QACH,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAA;KACxB,CAAA;IACD,GAAG,EAAE;QACH;;WAEG;QACH,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,CAAA;KACnB,CAAA;IACD,MAAM,EAAE,CAAC,CAAA;CACV,CAAA"}
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,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: {
@@ -1 +1 @@
1
- {"version":3,"file":"patch-endpoint.d.ts","sourceRoot":"","sources":["../../src/endpoint-models/patch-endpoint.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,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
+ {"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;;GAEG;AACH,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"}
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"}
@@ -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"}
@@ -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) {
@@ -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
- * Serialize steps:
3
- * 1. Stringify the value (even primitives, ensure type safety), e.g.: { foo: 'bar😉' } => '{"foo":"bar😉"}'
4
- * 2. Encode as an URI Component, e.g.: ''{"foo":"bar😉"}'' => '%7B%22foo%22%3A%22bar%F0%9F%98%89%22%7D'
5
- * 3. Unescape the URI Component, e.g.: '%7B%22foo%22%3A%22bar%F0%9F%98%89%22%7D' => '{"foo":"barð\x9F\x98\x89"}' - This and the first encodeURIComponent is needed because btoa only supports ASCII characters. We also don't want to encode the whole JSON string to keep a reasonable string length
6
- * 4. Encode the string as base64, e.g.: '{"foo":"barð\x9F\x98\x89"}' => 'eyJmb28iOiJiYXLwn5iJIn0='
7
- * 5. Encode as an URL Param: 'eyJmb28iOiJiYXLwn5iJIn0=' => 'eyJmb28iOiJiYXLwn5iJIn0%3D'
8
- * @param value The value to encode
9
- * @returns The encoded value that can be used as an URL search parameter
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;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,GAAG,WACsC,CAAA;AAE/E,eAAO,MAAM,sBAAsB,GAAI,CAAC,SAAS,MAAM,EAAE,aAAa,CAAC,KAAG,MAQzE,CAAA"}
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
- * Serialize steps:
3
- * 1. Stringify the value (even primitives, ensure type safety), e.g.: { foo: 'bar😉' } => '{"foo":"bar😉"}'
4
- * 2. Encode as an URI Component, e.g.: ''{"foo":"bar😉"}'' => '%7B%22foo%22%3A%22bar%F0%9F%98%89%22%7D'
5
- * 3. Unescape the URI Component, e.g.: '%7B%22foo%22%3A%22bar%F0%9F%98%89%22%7D' => '{"foo":"barð\x9F\x98\x89"}' - This and the first encodeURIComponent is needed because btoa only supports ASCII characters. We also don't want to encode the whole JSON string to keep a reasonable string length
6
- * 4. Encode the string as base64, e.g.: '{"foo":"barð\x9F\x98\x89"}' => 'eyJmb28iOiJiYXLwn5iJIn0='
7
- * 5. Encode as an URL Param: 'eyJmb28iOiJiYXLwn5iJIn0=' => 'eyJmb28iOiJiYXLwn5iJIn0%3D'
8
- * @param value The value to encode
9
- * @returns The encoded value that can be used as an URL search parameter
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;;;;;;;;;GASG;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,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"}
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": "10.0.0",
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": "^17.0.0",
42
- "@furystack/inject": "^13.0.0"
41
+ "@furystack/core": "^17.1.0",
42
+ "@furystack/inject": "^13.0.1"
43
43
  },
44
44
  "devDependencies": {
45
- "@types/node": "^25.6.0",
45
+ "@types/node": "^25.9.1",
46
46
  "typescript": "^6.0.3",
47
- "vitest": "^4.1.4"
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
- * Represents the definition of an API endpoint, including its path, schema, and schema name.
25
- * The HTTP method is now implicit in the structure key.
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
- * The OpenAPI security scheme names required for this endpoint.
65
- * When present, used by `generateOpenApiDocument` to emit accurate per-operation `security`.
66
- * When absent, falls back to the `isAuthenticated` boolean behavior.
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
- * Decodes a URL-safe base64 encoded JSON string back to its original value
5
- * Decoding steps: See the encoding steps in reverse order
6
- * @param value The value to decode
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) as Record<string, unknown>
25
+ return Object.fromEntries(params)
23
26
  }
@@ -1,6 +1,4 @@
1
- /**
2
- * Endpoint model for deleting entities
3
- */
1
+ /** DELETE-by-id endpoint contract. */
4
2
  export type DeleteEndpoint<T, TPrimaryKey extends keyof T> = {
5
3
  url: { id: T[TPrimaryKey] }
6
4
  result: object
@@ -1,22 +1,15 @@
1
1
  import type { FindOptions } from '@furystack/core'
2
2
 
3
3
  /**
4
- * Response Model for GetCollection
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
@@ -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
- * Serialize steps:
3
- * 1. Stringify the value (even primitives, ensure type safety), e.g.: { foo: 'bar😉' } => '{"foo":"bar😉"}'
4
- * 2. Encode as an URI Component, e.g.: ''{"foo":"bar😉"}'' => '%7B%22foo%22%3A%22bar%F0%9F%98%89%22%7D'
5
- * 3. Unescape the URI Component, e.g.: '%7B%22foo%22%3A%22bar%F0%9F%98%89%22%7D' => '{"foo":"barð\x9F\x98\x89"}' - This and the first encodeURIComponent is needed because btoa only supports ASCII characters. We also don't want to encode the whole JSON string to keep a reasonable string length
6
- * 4. Encode the string as base64, e.g.: '{"foo":"barð\x9F\x98\x89"}' => 'eyJmb28iOiJiYXLwn5iJIn0='
7
- * 5. Encode as an URL Param: 'eyJmb28iOiJiYXLwn5iJIn0=' => 'eyJmb28iOiJiYXLwn5iJIn0%3D'
8
- * @param value The value to encode
9
- * @returns The encoded value that can be used as an URL search parameter
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(