@xfcfam/xf-server-http 0.1.0

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 (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +240 -0
  3. package/dist/index.d.ts +63 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +55 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/src/api/A.d.ts +18 -0
  8. package/dist/src/api/A.d.ts.map +1 -0
  9. package/dist/src/api/A.js +18 -0
  10. package/dist/src/api/A.js.map +1 -0
  11. package/dist/src/api/general/GraphQLService.d.ts +48 -0
  12. package/dist/src/api/general/GraphQLService.d.ts.map +1 -0
  13. package/dist/src/api/general/GraphQLService.js +43 -0
  14. package/dist/src/api/general/GraphQLService.js.map +1 -0
  15. package/dist/src/api/general/ObjectRestService.d.ts +196 -0
  16. package/dist/src/api/general/ObjectRestService.d.ts.map +1 -0
  17. package/dist/src/api/general/ObjectRestService.js +289 -0
  18. package/dist/src/api/general/ObjectRestService.js.map +1 -0
  19. package/dist/src/api/general/RestService.d.ts +62 -0
  20. package/dist/src/api/general/RestService.d.ts.map +1 -0
  21. package/dist/src/api/general/RestService.js +75 -0
  22. package/dist/src/api/general/RestService.js.map +1 -0
  23. package/dist/src/api/general/WebSocketService.d.ts +42 -0
  24. package/dist/src/api/general/WebSocketService.d.ts.map +1 -0
  25. package/dist/src/api/general/WebSocketService.js +45 -0
  26. package/dist/src/api/general/WebSocketService.js.map +1 -0
  27. package/dist/src/api/utils/FileResponseUtils.d.ts +66 -0
  28. package/dist/src/api/utils/FileResponseUtils.d.ts.map +1 -0
  29. package/dist/src/api/utils/FileResponseUtils.js +82 -0
  30. package/dist/src/api/utils/FileResponseUtils.js.map +1 -0
  31. package/dist/src/api/utils/HttpStatusUtils.d.ts +36 -0
  32. package/dist/src/api/utils/HttpStatusUtils.d.ts.map +1 -0
  33. package/dist/src/api/utils/HttpStatusUtils.js +40 -0
  34. package/dist/src/api/utils/HttpStatusUtils.js.map +1 -0
  35. package/dist/src/api/utils/ResponseUtils.d.ts +61 -0
  36. package/dist/src/api/utils/ResponseUtils.d.ts.map +1 -0
  37. package/dist/src/api/utils/ResponseUtils.js +81 -0
  38. package/dist/src/api/utils/ResponseUtils.js.map +1 -0
  39. package/dist/src/api/utils/SchemaValidatorUtils.d.ts +48 -0
  40. package/dist/src/api/utils/SchemaValidatorUtils.d.ts.map +1 -0
  41. package/dist/src/api/utils/SchemaValidatorUtils.js +52 -0
  42. package/dist/src/api/utils/SchemaValidatorUtils.js.map +1 -0
  43. package/dist/src/api/utils/SseUtils.d.ts +57 -0
  44. package/dist/src/api/utils/SseUtils.d.ts.map +1 -0
  45. package/dist/src/api/utils/SseUtils.js +78 -0
  46. package/dist/src/api/utils/SseUtils.js.map +1 -0
  47. package/dist/src/business/B.d.ts +18 -0
  48. package/dist/src/business/B.d.ts.map +1 -0
  49. package/dist/src/business/B.js +18 -0
  50. package/dist/src/business/B.js.map +1 -0
  51. package/dist/src/business/general/HttpServerBusiness.d.ts +190 -0
  52. package/dist/src/business/general/HttpServerBusiness.d.ts.map +1 -0
  53. package/dist/src/business/general/HttpServerBusiness.js +364 -0
  54. package/dist/src/business/general/HttpServerBusiness.js.map +1 -0
  55. package/dist/src/business/transfers/BadRequestException.d.ts +13 -0
  56. package/dist/src/business/transfers/BadRequestException.d.ts.map +1 -0
  57. package/dist/src/business/transfers/BadRequestException.js +16 -0
  58. package/dist/src/business/transfers/BadRequestException.js.map +1 -0
  59. package/dist/src/business/transfers/ForbiddenException.d.ts +13 -0
  60. package/dist/src/business/transfers/ForbiddenException.d.ts.map +1 -0
  61. package/dist/src/business/transfers/ForbiddenException.js +16 -0
  62. package/dist/src/business/transfers/ForbiddenException.js.map +1 -0
  63. package/dist/src/business/transfers/GraphQLConfig.d.ts +25 -0
  64. package/dist/src/business/transfers/GraphQLConfig.d.ts.map +1 -0
  65. package/dist/src/business/transfers/GraphQLConfig.js +2 -0
  66. package/dist/src/business/transfers/GraphQLConfig.js.map +1 -0
  67. package/dist/src/business/transfers/HttpException.d.ts +23 -0
  68. package/dist/src/business/transfers/HttpException.d.ts.map +1 -0
  69. package/dist/src/business/transfers/HttpException.js +27 -0
  70. package/dist/src/business/transfers/HttpException.js.map +1 -0
  71. package/dist/src/business/transfers/HttpMethod.d.ts +7 -0
  72. package/dist/src/business/transfers/HttpMethod.d.ts.map +1 -0
  73. package/dist/src/business/transfers/HttpMethod.js +2 -0
  74. package/dist/src/business/transfers/HttpMethod.js.map +1 -0
  75. package/dist/src/business/transfers/HttpRequest.d.ts +35 -0
  76. package/dist/src/business/transfers/HttpRequest.d.ts.map +1 -0
  77. package/dist/src/business/transfers/HttpRequest.js +2 -0
  78. package/dist/src/business/transfers/HttpRequest.js.map +1 -0
  79. package/dist/src/business/transfers/HttpResponse.d.ts +28 -0
  80. package/dist/src/business/transfers/HttpResponse.d.ts.map +1 -0
  81. package/dist/src/business/transfers/HttpResponse.js +2 -0
  82. package/dist/src/business/transfers/HttpResponse.js.map +1 -0
  83. package/dist/src/business/transfers/InternalServerException.d.ts +13 -0
  84. package/dist/src/business/transfers/InternalServerException.d.ts.map +1 -0
  85. package/dist/src/business/transfers/InternalServerException.js +16 -0
  86. package/dist/src/business/transfers/InternalServerException.js.map +1 -0
  87. package/dist/src/business/transfers/MultipartPart.d.ts +38 -0
  88. package/dist/src/business/transfers/MultipartPart.d.ts.map +1 -0
  89. package/dist/src/business/transfers/MultipartPart.js +2 -0
  90. package/dist/src/business/transfers/MultipartPart.js.map +1 -0
  91. package/dist/src/business/transfers/NotFoundException.d.ts +13 -0
  92. package/dist/src/business/transfers/NotFoundException.d.ts.map +1 -0
  93. package/dist/src/business/transfers/NotFoundException.js +16 -0
  94. package/dist/src/business/transfers/NotFoundException.js.map +1 -0
  95. package/dist/src/business/transfers/Route.d.ts +25 -0
  96. package/dist/src/business/transfers/Route.d.ts.map +1 -0
  97. package/dist/src/business/transfers/Route.js +2 -0
  98. package/dist/src/business/transfers/Route.js.map +1 -0
  99. package/dist/src/business/transfers/UnauthorizedException.d.ts +13 -0
  100. package/dist/src/business/transfers/UnauthorizedException.d.ts.map +1 -0
  101. package/dist/src/business/transfers/UnauthorizedException.js +16 -0
  102. package/dist/src/business/transfers/UnauthorizedException.js.map +1 -0
  103. package/dist/src/business/transfers/WebSocketConnection.d.ts +40 -0
  104. package/dist/src/business/transfers/WebSocketConnection.d.ts.map +1 -0
  105. package/dist/src/business/transfers/WebSocketConnection.js +2 -0
  106. package/dist/src/business/transfers/WebSocketConnection.js.map +1 -0
  107. package/dist/src/repository/R.d.ts +18 -0
  108. package/dist/src/repository/R.d.ts.map +1 -0
  109. package/dist/src/repository/R.js +18 -0
  110. package/dist/src/repository/R.js.map +1 -0
  111. package/package.json +62 -0
@@ -0,0 +1,196 @@
1
+ import { RestService } from './RestService.js';
2
+ import type { HttpRequest } from '../../business/transfers/HttpRequest.js';
3
+ import type { HttpResponse } from '../../business/transfers/HttpResponse.js';
4
+ import type { HttpHandler } from '../../business/transfers/Route.js';
5
+ /**
6
+ * Body parser: given the raw bytes of a request and the value of the
7
+ * `Content-Type` header, produce the parsed JS object. May throw on
8
+ * invalid input; the pipeline wraps the failure in a
9
+ * `BadRequestException`.
10
+ */
11
+ export type BodyParser = (bytes: Uint8Array, contentType: string) => unknown;
12
+ /**
13
+ * Body serializer: given a JS value and the negotiated media type,
14
+ * produce the byte buffer to send on the wire. Mirror of
15
+ * {@link BodyParser}.
16
+ */
17
+ export type BodySerializer = (value: unknown, mediaType: string) => Uint8Array;
18
+ /**
19
+ * Configuration accepted by {@link ObjectRestService}'s constructor.
20
+ * All keys are optional — sensible JSON defaults are wired
21
+ * automatically.
22
+ */
23
+ export interface ObjectRestOptions {
24
+ /**
25
+ * Content-Type → parser map. Keys are matched case-insensitively
26
+ * against the request's `Content-Type` (the media type before any
27
+ * `;` parameters). User entries override the built-in JSON parser.
28
+ *
29
+ * To support XML / CSV / YAML, supply the parser yourself and let
30
+ * the consumer choose the underlying library:
31
+ *
32
+ * ```ts
33
+ * new MyService({
34
+ * parsers: new Map([
35
+ * ['application/xml', (bytes) => xmlParser(decode(bytes))],
36
+ * ]),
37
+ * })
38
+ * ```
39
+ */
40
+ readonly parsers?: ReadonlyMap<string, BodyParser>;
41
+ /**
42
+ * Media-type → serializer map. Selected by the response's
43
+ * `Content-Type` (defaulting to `application/json`).
44
+ */
45
+ readonly serializers?: ReadonlyMap<string, BodySerializer>;
46
+ /**
47
+ * Media type to use for serialisation when the response does not
48
+ * carry an explicit `Content-Type`. Default `'application/json'`.
49
+ */
50
+ readonly defaultMediaType?: string;
51
+ /**
52
+ * When `true`, the built-in JSON parser revives ISO-8601 date strings
53
+ * into `Date` objects (via {@link ObjectRestService.dateReviver}), so
54
+ * handlers receive real `Date`s on the request side. Serialising
55
+ * `Date`s back to string is automatic — see {@link formatDate} — so
56
+ * dates round-trip with this single flag. Default `false`.
57
+ */
58
+ readonly reviveDates?: boolean;
59
+ /**
60
+ * How a `Date` is rendered to text when serialising the response body.
61
+ * The developer decides the wire format: the default is ISO-8601
62
+ * (`date.toISOString()`), but any function works — date-only, epoch
63
+ * millis, a locale string, or a custom formatter.
64
+ *
65
+ * ```ts
66
+ * new MyService({ formatDate: (d) => d.toISOString().slice(0, 10) }) // YYYY-MM-DD
67
+ * new MyService({ formatDate: (d) => String(d.getTime()) }) // epoch ms
68
+ * ```
69
+ *
70
+ * Applied to every `Date` found anywhere in the response body (nested
71
+ * objects and arrays included). Default: ISO-8601.
72
+ */
73
+ readonly formatDate?: (date: Date) => string;
74
+ }
75
+ /**
76
+ * Generalization for Interaction Layer components that operate on
77
+ * **parsed JS objects** on both sides of the wire — the typical REST
78
+ * service.
79
+ *
80
+ * Extends {@link RestService} with automatic body parsing and
81
+ * serialisation. Use `this.object(handler)` when registering a route
82
+ * to wrap the handler in the full parse → handle → serialize pipeline:
83
+ *
84
+ * ```ts
85
+ * B.server.post('/users', this.object(this.createUser))
86
+ * ```
87
+ *
88
+ * Combine with `this.wrap(this.object(handler))` to add the
89
+ * per-service `onRequest` / `onResponse` hooks on top.
90
+ *
91
+ * **Built-in**: JSON parse + serialize, with developer-chosen date
92
+ * handling (see {@link ObjectRestService.dateReviver}, the `reviveDates`
93
+ * option, and `formatDate` in {@link ObjectRestOptions}). XML/CSV/YAML
94
+ * parsing and any other serializers are wired by passing them in
95
+ * {@link ObjectRestOptions}, keeping the package zero-dependency.
96
+ *
97
+ * Stream bodies (request or response) bypass parsing/serialisation:
98
+ * if the request body is a `ReadableStream` the handler may consume
99
+ * it directly; if the handler returns a `ReadableStream` or a
100
+ * `Uint8Array` it is sent verbatim.
101
+ *
102
+ * @example
103
+ * ```ts
104
+ * import { ObjectRestService, HttpStatusUtils } from '@xfcfam/xf-server'
105
+ * import { B } from '../../business/B.js'
106
+ *
107
+ * export class UsersRestService extends ObjectRestService {
108
+ * override async init(): Promise<void> {
109
+ * B.server.get('/users/:id', this.wrap(this.getUser))
110
+ * B.server.post('/users', this.object(this.createUser))
111
+ * }
112
+ *
113
+ * private async getUser(req: HttpRequest): Promise<HttpResponse> {
114
+ * const user = await B.usersBusiness.findById(req.params.id!)
115
+ * return { status: HttpStatusUtils.OK, body: user }
116
+ * }
117
+ *
118
+ * private async createUser(req: HttpRequest): Promise<HttpResponse> {
119
+ * const body = req.body as { name: string; email: string }
120
+ * const user = await B.usersBusiness.create(body)
121
+ * return { status: HttpStatusUtils.CREATED, body: user }
122
+ * }
123
+ * }
124
+ * ```
125
+ */
126
+ export declare abstract class ObjectRestService extends RestService {
127
+ protected readonly parsers: ReadonlyMap<string, BodyParser>;
128
+ protected readonly serializers: ReadonlyMap<string, BodySerializer>;
129
+ protected readonly defaultMediaType: string;
130
+ /** Developer-chosen `Date` → text formatter for the JSON serializer. */
131
+ protected readonly formatDate: (date: Date) => string;
132
+ constructor(options?: ObjectRestOptions);
133
+ /**
134
+ * Wrap a handler in the body parse → handle → serialize pipeline,
135
+ * plus the per-service `onRequest` / `onResponse` / `onError` hooks.
136
+ *
137
+ * This is the recommended registration method for
138
+ * `ObjectRestService` handlers:
139
+ *
140
+ * ```ts
141
+ * B.server.post('/users', this.object(this.createUser))
142
+ * ```
143
+ *
144
+ * The pipeline runs:
145
+ * 1. `onRequest(req)` (per-service hook)
146
+ * 2. parse request body (bytes → object, by Content-Type)
147
+ * 3. handler
148
+ * 4. `onResponse(req, res)` (per-service hook)
149
+ * 5. serialize response body (object → bytes, by Content-Type)
150
+ *
151
+ * `onResponse` fires before serialisation so envelope wrappers see
152
+ * the original payload.
153
+ *
154
+ * Use `this.wrap(handler)` instead when the handler already manages
155
+ * its own bytes (upload / download / SSE endpoints).
156
+ */
157
+ protected object<TReqBody = unknown, TResBody = unknown>(handler: HttpHandler<TReqBody, TResBody>): HttpHandler;
158
+ /**
159
+ * Decode the request body to a JS value using the registered
160
+ * parser for `Content-Type`. Stream bodies are buffered first.
161
+ * Exposed as `protected` so subclasses can call it directly when
162
+ * composing custom pipelines.
163
+ */
164
+ protected parseRequestBody(request: HttpRequest): Promise<HttpRequest>;
165
+ /**
166
+ * Encode the response body to bytes using the registered serializer
167
+ * for the response's `Content-Type` (or the default media type).
168
+ * Streams and Uint8Arrays are passed through unchanged. Exposed as
169
+ * `protected` so subclasses can call it directly when composing
170
+ * custom pipelines.
171
+ */
172
+ protected serializeResponseBody(response: HttpResponse): HttpResponse;
173
+ private static parseJson;
174
+ private static serializeJson;
175
+ private static toIsoString;
176
+ /**
177
+ * Deep-copy `value`, replacing every `Date` with `formatDate(date)` so
178
+ * a developer-chosen date format survives `JSON.stringify` (which would
179
+ * otherwise call `Date.toJSON` and force ISO-8601).
180
+ */
181
+ private static applyDateFormat;
182
+ /**
183
+ * JSON reviver that turns ISO-8601 date strings into `Date` objects —
184
+ * the developer shortcut for date handling. Use it directly with
185
+ * `JSON.parse(text, ObjectRestService.dateReviver)`, or set
186
+ * `reviveDates: true` in {@link ObjectRestOptions} to apply it to the
187
+ * built-in JSON parser. The inverse is automatic: a `Date` serializes
188
+ * back to text via the `formatDate` option (ISO-8601 by default).
189
+ */
190
+ static dateReviver(_key: string, value: unknown): unknown;
191
+ private static readonly ISO_DATE_RE;
192
+ private static mediaTypeOf;
193
+ private static isReadableStream;
194
+ private static collect;
195
+ }
196
+ //# sourceMappingURL=ObjectRestService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ObjectRestService.d.ts","sourceRoot":"","sources":["../../../../src/api/general/ObjectRestService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAA;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0CAA0C,CAAA;AAC5E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAA;AAGpE;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,KAAK,OAAO,CAAA;AAE5E;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,KAAK,UAAU,CAAA;AAE9E;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IAClD;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;IAC1D;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAClC;;;;;;OAMG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAA;IAC9B;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAAA;CAC7C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,8BAAsB,iBAAkB,SAAQ,WAAW;IACzD,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IAC3D,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;IACnE,SAAS,CAAC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IAC3C,wEAAwE;IACxE,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAAA;gBAEzC,OAAO,GAAE,iBAAsB;IA2B3C;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,SAAS,CAAC,MAAM,CAAC,QAAQ,GAAG,OAAO,EAAE,QAAQ,GAAG,OAAO,EACrD,OAAO,EAAE,WAAW,CAAC,QAAQ,EAAE,QAAQ,CAAC,GACvC,WAAW;IAiBd;;;;;OAKG;cACa,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IAgC5E;;;;;;OAMG;IACH,SAAS,CAAC,qBAAqB,CAAC,QAAQ,EAAE,YAAY,GAAG,YAAY;IA6BrE,OAAO,CAAC,MAAM,CAAC,SAAS;IAKxB,OAAO,CAAC,MAAM,CAAC,aAAa;IAS5B,OAAO,CAAC,MAAM,CAAC,WAAW;IAI1B;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;IAe9B;;;;;;;OAOG;IACH,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO;IAQzD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CACqC;IAExE,OAAO,CAAC,MAAM,CAAC,WAAW;IAO1B,OAAO,CAAC,MAAM,CAAC,gBAAgB;mBAMV,OAAO;CAc7B"}
@@ -0,0 +1,289 @@
1
+ import { RestService } from './RestService.js';
2
+ import { BadRequestException } from '../../business/transfers/BadRequestException.js';
3
+ /**
4
+ * Generalization for Interaction Layer components that operate on
5
+ * **parsed JS objects** on both sides of the wire — the typical REST
6
+ * service.
7
+ *
8
+ * Extends {@link RestService} with automatic body parsing and
9
+ * serialisation. Use `this.object(handler)` when registering a route
10
+ * to wrap the handler in the full parse → handle → serialize pipeline:
11
+ *
12
+ * ```ts
13
+ * B.server.post('/users', this.object(this.createUser))
14
+ * ```
15
+ *
16
+ * Combine with `this.wrap(this.object(handler))` to add the
17
+ * per-service `onRequest` / `onResponse` hooks on top.
18
+ *
19
+ * **Built-in**: JSON parse + serialize, with developer-chosen date
20
+ * handling (see {@link ObjectRestService.dateReviver}, the `reviveDates`
21
+ * option, and `formatDate` in {@link ObjectRestOptions}). XML/CSV/YAML
22
+ * parsing and any other serializers are wired by passing them in
23
+ * {@link ObjectRestOptions}, keeping the package zero-dependency.
24
+ *
25
+ * Stream bodies (request or response) bypass parsing/serialisation:
26
+ * if the request body is a `ReadableStream` the handler may consume
27
+ * it directly; if the handler returns a `ReadableStream` or a
28
+ * `Uint8Array` it is sent verbatim.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * import { ObjectRestService, HttpStatusUtils } from '@xfcfam/xf-server'
33
+ * import { B } from '../../business/B.js'
34
+ *
35
+ * export class UsersRestService extends ObjectRestService {
36
+ * override async init(): Promise<void> {
37
+ * B.server.get('/users/:id', this.wrap(this.getUser))
38
+ * B.server.post('/users', this.object(this.createUser))
39
+ * }
40
+ *
41
+ * private async getUser(req: HttpRequest): Promise<HttpResponse> {
42
+ * const user = await B.usersBusiness.findById(req.params.id!)
43
+ * return { status: HttpStatusUtils.OK, body: user }
44
+ * }
45
+ *
46
+ * private async createUser(req: HttpRequest): Promise<HttpResponse> {
47
+ * const body = req.body as { name: string; email: string }
48
+ * const user = await B.usersBusiness.create(body)
49
+ * return { status: HttpStatusUtils.CREATED, body: user }
50
+ * }
51
+ * }
52
+ * ```
53
+ */
54
+ export class ObjectRestService extends RestService {
55
+ parsers;
56
+ serializers;
57
+ defaultMediaType;
58
+ /** Developer-chosen `Date` → text formatter for the JSON serializer. */
59
+ formatDate;
60
+ constructor(options = {}) {
61
+ super();
62
+ this.formatDate = options.formatDate ?? ObjectRestService.toIsoString;
63
+ const jsonParser = options.reviveDates === true
64
+ ? (bytes) => JSON.parse(new TextDecoder('utf-8').decode(bytes), ObjectRestService.dateReviver)
65
+ : ObjectRestService.parseJson;
66
+ const parsers = new Map([
67
+ ['application/json', jsonParser],
68
+ ]);
69
+ if (options.parsers !== undefined) {
70
+ for (const [k, v] of options.parsers)
71
+ parsers.set(k.toLowerCase(), v);
72
+ }
73
+ this.parsers = parsers;
74
+ const formatDate = this.formatDate;
75
+ const serializers = new Map([
76
+ ['application/json', (value) => ObjectRestService.serializeJson(value, formatDate)],
77
+ ]);
78
+ if (options.serializers !== undefined) {
79
+ for (const [k, v] of options.serializers)
80
+ serializers.set(k.toLowerCase(), v);
81
+ }
82
+ this.serializers = serializers;
83
+ this.defaultMediaType = options.defaultMediaType ?? 'application/json';
84
+ }
85
+ /**
86
+ * Wrap a handler in the body parse → handle → serialize pipeline,
87
+ * plus the per-service `onRequest` / `onResponse` / `onError` hooks.
88
+ *
89
+ * This is the recommended registration method for
90
+ * `ObjectRestService` handlers:
91
+ *
92
+ * ```ts
93
+ * B.server.post('/users', this.object(this.createUser))
94
+ * ```
95
+ *
96
+ * The pipeline runs:
97
+ * 1. `onRequest(req)` (per-service hook)
98
+ * 2. parse request body (bytes → object, by Content-Type)
99
+ * 3. handler
100
+ * 4. `onResponse(req, res)` (per-service hook)
101
+ * 5. serialize response body (object → bytes, by Content-Type)
102
+ *
103
+ * `onResponse` fires before serialisation so envelope wrappers see
104
+ * the original payload.
105
+ *
106
+ * Use `this.wrap(handler)` instead when the handler already manages
107
+ * its own bytes (upload / download / SSE endpoints).
108
+ */
109
+ object(handler) {
110
+ const bound = handler.bind(this);
111
+ return async (req) => {
112
+ let r = await this.onRequest(req);
113
+ try {
114
+ r = await this.parseRequestBody(r);
115
+ const res = await bound(r);
116
+ const afterHook = await this.onResponse(r, res);
117
+ return this.serializeResponseBody(afterHook);
118
+ }
119
+ catch (err) {
120
+ const resolved = await this.onError(r, err);
121
+ if (resolved !== undefined)
122
+ return resolved;
123
+ throw err;
124
+ }
125
+ };
126
+ }
127
+ /**
128
+ * Decode the request body to a JS value using the registered
129
+ * parser for `Content-Type`. Stream bodies are buffered first.
130
+ * Exposed as `protected` so subclasses can call it directly when
131
+ * composing custom pipelines.
132
+ */
133
+ async parseRequestBody(request) {
134
+ const body = request.body;
135
+ if (body === null || body === undefined)
136
+ return request;
137
+ // Multipart bodies are already an array of MultipartPart (the
138
+ // server adapter produced them before the parser ran). Pass
139
+ // through unchanged.
140
+ if (Array.isArray(body))
141
+ return request;
142
+ // Pass through non-bytes (e.g. already-parsed, or handler chose to consume the stream directly).
143
+ if (!(body instanceof Uint8Array) && !ObjectRestService.isReadableStream(body))
144
+ return request;
145
+ const bytes = body instanceof Uint8Array
146
+ ? body
147
+ : await ObjectRestService.collect(body);
148
+ if (bytes.byteLength === 0)
149
+ return { ...request, body: null };
150
+ const contentType = ObjectRestService.mediaTypeOf(request.headers['content-type']);
151
+ const parser = this.parsers.get(contentType.toLowerCase());
152
+ if (parser === undefined) {
153
+ // Unknown content-type → leave the bytes as-is; the handler may
154
+ // know what to do with them. Common case for binary uploads
155
+ // that bypass parsing.
156
+ return { ...request, body: bytes };
157
+ }
158
+ try {
159
+ const parsed = parser(bytes, contentType);
160
+ return { ...request, body: parsed };
161
+ }
162
+ catch (err) {
163
+ throw new BadRequestException(`Invalid ${contentType} body`, { detail: err.message });
164
+ }
165
+ }
166
+ /**
167
+ * Encode the response body to bytes using the registered serializer
168
+ * for the response's `Content-Type` (or the default media type).
169
+ * Streams and Uint8Arrays are passed through unchanged. Exposed as
170
+ * `protected` so subclasses can call it directly when composing
171
+ * custom pipelines.
172
+ */
173
+ serializeResponseBody(response) {
174
+ const body = response.body;
175
+ if (body === null || body === undefined)
176
+ return response;
177
+ if (body instanceof Uint8Array)
178
+ return response;
179
+ if (typeof body === 'string')
180
+ return response;
181
+ if (ObjectRestService.isReadableStream(body))
182
+ return response;
183
+ const headers = response.headers ?? {};
184
+ const explicit = headers['content-type'];
185
+ const mediaType = ObjectRestService.mediaTypeOf(explicit) || this.defaultMediaType;
186
+ const serializer = this.serializers.get(mediaType.toLowerCase());
187
+ if (serializer === undefined) {
188
+ // Fall back to JSON serialisation if the explicit media type
189
+ // is unknown — a sensible default rather than a 500.
190
+ return {
191
+ status: response.status,
192
+ headers: { ...headers, 'content-type': this.defaultMediaType },
193
+ body: ObjectRestService.serializeJson(body, this.formatDate),
194
+ };
195
+ }
196
+ return {
197
+ status: response.status,
198
+ headers: { ...headers, 'content-type': mediaType },
199
+ body: serializer(body, mediaType),
200
+ };
201
+ }
202
+ // ─── Helpers ────────────────────────────────────────────────
203
+ static parseJson(bytes, _ct) {
204
+ const text = new TextDecoder('utf-8').decode(bytes);
205
+ return JSON.parse(text);
206
+ }
207
+ static serializeJson(value, formatDate) {
208
+ // Fast path: the default ISO formatter matches `Date.toJSON`, so
209
+ // `JSON.stringify` already produces the right text — skip the walk.
210
+ const prepared = formatDate === ObjectRestService.toIsoString
211
+ ? value
212
+ : ObjectRestService.applyDateFormat(value, formatDate);
213
+ return new TextEncoder().encode(JSON.stringify(prepared));
214
+ }
215
+ static toIsoString(date) {
216
+ return date.toISOString();
217
+ }
218
+ /**
219
+ * Deep-copy `value`, replacing every `Date` with `formatDate(date)` so
220
+ * a developer-chosen date format survives `JSON.stringify` (which would
221
+ * otherwise call `Date.toJSON` and force ISO-8601).
222
+ */
223
+ static applyDateFormat(value, formatDate) {
224
+ if (value instanceof Date)
225
+ return formatDate(value);
226
+ if (Array.isArray(value)) {
227
+ return value.map(v => ObjectRestService.applyDateFormat(v, formatDate));
228
+ }
229
+ if (value !== null && typeof value === 'object') {
230
+ const out = {};
231
+ for (const [k, v] of Object.entries(value)) {
232
+ out[k] = ObjectRestService.applyDateFormat(v, formatDate);
233
+ }
234
+ return out;
235
+ }
236
+ return value;
237
+ }
238
+ /**
239
+ * JSON reviver that turns ISO-8601 date strings into `Date` objects —
240
+ * the developer shortcut for date handling. Use it directly with
241
+ * `JSON.parse(text, ObjectRestService.dateReviver)`, or set
242
+ * `reviveDates: true` in {@link ObjectRestOptions} to apply it to the
243
+ * built-in JSON parser. The inverse is automatic: a `Date` serializes
244
+ * back to text via the `formatDate` option (ISO-8601 by default).
245
+ */
246
+ static dateReviver(_key, value) {
247
+ if (typeof value === 'string' && ObjectRestService.ISO_DATE_RE.test(value)) {
248
+ const ms = Date.parse(value);
249
+ if (!Number.isNaN(ms))
250
+ return new Date(ms);
251
+ }
252
+ return value;
253
+ }
254
+ static ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$/;
255
+ static mediaTypeOf(contentType) {
256
+ const ct = Array.isArray(contentType) ? contentType[0] : contentType;
257
+ if (typeof ct !== 'string' || ct.length === 0)
258
+ return '';
259
+ const semi = ct.indexOf(';');
260
+ return (semi < 0 ? ct : ct.substring(0, semi)).trim().toLowerCase();
261
+ }
262
+ static isReadableStream(value) {
263
+ return typeof value === 'object'
264
+ && value !== null
265
+ && typeof value.getReader === 'function';
266
+ }
267
+ static async collect(stream) {
268
+ const reader = stream.getReader();
269
+ const chunks = [];
270
+ let total = 0;
271
+ while (true) {
272
+ const { done, value } = await reader.read();
273
+ if (done)
274
+ break;
275
+ if (value !== undefined) {
276
+ chunks.push(value);
277
+ total += value.byteLength;
278
+ }
279
+ }
280
+ const out = new Uint8Array(total);
281
+ let offset = 0;
282
+ for (const chunk of chunks) {
283
+ out.set(chunk, offset);
284
+ offset += chunk.byteLength;
285
+ }
286
+ return out;
287
+ }
288
+ }
289
+ //# sourceMappingURL=ObjectRestService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ObjectRestService.js","sourceRoot":"","sources":["../../../../src/api/general/ObjectRestService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import { EntryService } from '@xfcfam/xf-server';
2
+ import type { HttpRequest } from '../../business/transfers/HttpRequest.js';
3
+ import type { HttpResponse } from '../../business/transfers/HttpResponse.js';
4
+ /**
5
+ * Interaction-Layer Generalization for components that expose a set of
6
+ * HTTP endpoints over the Business-Layer server.
7
+ *
8
+ * The HTTP specialisation of {@link EntryService} (from
9
+ * `@xfcfam/xf-server`): it fixes the request/response types to
10
+ * `HttpRequest` / `HttpResponse` and adds HTTP response builders. The
11
+ * per-service pipeline (`wrap`, `onRequest` / `onResponse` / `onError`)
12
+ * is inherited unchanged.
13
+ *
14
+ * Concrete subclasses register their endpoints by calling
15
+ * `B.server.get(path, this.wrap(handler))` (or `post`, `put`, …) from
16
+ * within `init()`. The server (`HttpServerBusiness`) holds the route
17
+ * registry and starts Fastify; `RestService` contributes handlers via
18
+ * the push registration API.
19
+ *
20
+ * Body handling is **raw**: the request body arrives either as a
21
+ * `Uint8Array` (when fully buffered) or as a `ReadableStream<Uint8Array>`
22
+ * (when streamed). For the common case of JSON REST APIs that work with
23
+ * parsed objects on both sides, extend {@link ObjectRestService} — it
24
+ * adds automatic content-negotiation parsing and serialisation.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * import { RestService, HttpStatusUtils } from '@xfcfam/xf-server-http'
29
+ * import { B } from '../../business/B.js'
30
+ *
31
+ * export class HealthRestService extends RestService {
32
+ * override async init(): Promise<void> {
33
+ * B.server.get('/health', this.wrap(this.health))
34
+ * }
35
+ *
36
+ * private async health(_req: HttpRequest): Promise<HttpResponse> {
37
+ * return { status: HttpStatusUtils.OK, body: { status: 'ok' } }
38
+ * }
39
+ * }
40
+ * ```
41
+ */
42
+ export declare abstract class RestService extends EntryService<HttpRequest, HttpResponse> {
43
+ /**
44
+ * Convenience: build a successful response. Default status 200.
45
+ */
46
+ protected ok<T>(body: T, headers?: Record<string, string>): HttpResponse<T>;
47
+ /**
48
+ * Convenience: build a failure response. Default status 500.
49
+ */
50
+ protected ko<T>(body: T, status?: number, headers?: Record<string, string>): HttpResponse<T>;
51
+ /**
52
+ * Convenience: build a no-body 204. Use after successful mutations
53
+ * that don't return a payload.
54
+ */
55
+ protected empty(headers?: Record<string, string>): HttpResponse;
56
+ /**
57
+ * Convenience: throw an `InternalServerException`. Use only when no
58
+ * more specific HttpException applies.
59
+ */
60
+ protected fail(message: string, body?: unknown): never;
61
+ }
62
+ //# sourceMappingURL=RestService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RestService.d.ts","sourceRoot":"","sources":["../../../../src/api/general/RestService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAA;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0CAA0C,CAAA;AAG5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,8BAAsB,WAAY,SAAQ,YAAY,CAAC,WAAW,EAAE,YAAY,CAAC;IAC/E;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC;IAO3E;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,SAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC;IAOzF;;;OAGG;IACH,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY;IAI/D;;;OAGG;IACH,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,KAAK;CAGvD"}
@@ -0,0 +1,75 @@
1
+ import { EntryService } from '@xfcfam/xf-server';
2
+ import { InternalServerException } from '../../business/transfers/InternalServerException.js';
3
+ /**
4
+ * Interaction-Layer Generalization for components that expose a set of
5
+ * HTTP endpoints over the Business-Layer server.
6
+ *
7
+ * The HTTP specialisation of {@link EntryService} (from
8
+ * `@xfcfam/xf-server`): it fixes the request/response types to
9
+ * `HttpRequest` / `HttpResponse` and adds HTTP response builders. The
10
+ * per-service pipeline (`wrap`, `onRequest` / `onResponse` / `onError`)
11
+ * is inherited unchanged.
12
+ *
13
+ * Concrete subclasses register their endpoints by calling
14
+ * `B.server.get(path, this.wrap(handler))` (or `post`, `put`, …) from
15
+ * within `init()`. The server (`HttpServerBusiness`) holds the route
16
+ * registry and starts Fastify; `RestService` contributes handlers via
17
+ * the push registration API.
18
+ *
19
+ * Body handling is **raw**: the request body arrives either as a
20
+ * `Uint8Array` (when fully buffered) or as a `ReadableStream<Uint8Array>`
21
+ * (when streamed). For the common case of JSON REST APIs that work with
22
+ * parsed objects on both sides, extend {@link ObjectRestService} — it
23
+ * adds automatic content-negotiation parsing and serialisation.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * import { RestService, HttpStatusUtils } from '@xfcfam/xf-server-http'
28
+ * import { B } from '../../business/B.js'
29
+ *
30
+ * export class HealthRestService extends RestService {
31
+ * override async init(): Promise<void> {
32
+ * B.server.get('/health', this.wrap(this.health))
33
+ * }
34
+ *
35
+ * private async health(_req: HttpRequest): Promise<HttpResponse> {
36
+ * return { status: HttpStatusUtils.OK, body: { status: 'ok' } }
37
+ * }
38
+ * }
39
+ * ```
40
+ */
41
+ export class RestService extends EntryService {
42
+ /**
43
+ * Convenience: build a successful response. Default status 200.
44
+ */
45
+ ok(body, headers) {
46
+ const out = headers === undefined
47
+ ? { status: 200, body }
48
+ : { status: 200, body, headers };
49
+ return out;
50
+ }
51
+ /**
52
+ * Convenience: build a failure response. Default status 500.
53
+ */
54
+ ko(body, status = 500, headers) {
55
+ const out = headers === undefined
56
+ ? { status, body }
57
+ : { status, body, headers };
58
+ return out;
59
+ }
60
+ /**
61
+ * Convenience: build a no-body 204. Use after successful mutations
62
+ * that don't return a payload.
63
+ */
64
+ empty(headers) {
65
+ return headers === undefined ? { status: 204 } : { status: 204, headers };
66
+ }
67
+ /**
68
+ * Convenience: throw an `InternalServerException`. Use only when no
69
+ * more specific HttpException applies.
70
+ */
71
+ fail(message, body) {
72
+ throw new InternalServerException(message, body);
73
+ }
74
+ }
75
+ //# sourceMappingURL=RestService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RestService.js","sourceRoot":"","sources":["../../../../src/api/general/RestService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGhD,OAAO,EAAE,uBAAuB,EAAE,MAAM,qDAAqD,CAAA;AAE7F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,OAAgB,WAAY,SAAQ,YAAuC;IAC/E;;OAEG;IACO,EAAE,CAAI,IAAO,EAAE,OAAgC;QACvD,MAAM,GAAG,GAAoB,OAAO,KAAK,SAAS;YAChD,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE;YACvB,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;QAClC,OAAO,GAAG,CAAA;IACZ,CAAC;IAED;;OAEG;IACO,EAAE,CAAI,IAAO,EAAE,MAAM,GAAG,GAAG,EAAE,OAAgC;QACrE,MAAM,GAAG,GAAoB,OAAO,KAAK,SAAS;YAChD,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE;YAClB,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;QAC7B,OAAO,GAAG,CAAA;IACZ,CAAC;IAED;;;OAGG;IACO,KAAK,CAAC,OAAgC;QAC9C,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,CAAA;IAC3E,CAAC;IAED;;;OAGG;IACO,IAAI,CAAC,OAAe,EAAE,IAAc;QAC5C,MAAM,IAAI,uBAAuB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;IAClD,CAAC;CACF"}