@remit/backend 0.0.94 → 0.0.96

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/src/response.ts CHANGED
@@ -7,23 +7,50 @@ import {
7
7
  resolveAllowedOrigin,
8
8
  } from "./request-context.js";
9
9
 
10
- export const formatResponse = (
11
- body: Record<string, unknown>,
12
- statusCode = 200,
13
- ): APIGatewayProxyResult => {
14
- if (body.statusCode && typeof body.statusCode === "number") {
15
- statusCode = body.statusCode;
16
- }
17
-
18
- if ("body" in body && body.body && typeof body.body === "object") {
19
- body = body.body as Record<string, unknown>;
20
- }
10
+ const RAW_API_RESPONSE: unique symbol = Symbol("remit.rawApiResponse");
11
+
12
+ /**
13
+ * A response whose bytes are not JSON — an iCalendar feed, and whatever later
14
+ * joins it.
15
+ *
16
+ * Every other handler returns a plain object and lets this module decide the
17
+ * status, the content type and the serialization. That is the right default and
18
+ * stays the default; a handler that has to own its own media type says so with
19
+ * `rawApiResponse` rather than by returning a shape this one has to guess at.
20
+ * The CORS and correlation headers are still added here, so a raw response is
21
+ * not a way around them.
22
+ */
23
+ export interface RawApiResponse {
24
+ statusCode: number;
25
+ headers: Record<string, string>;
26
+ body: string;
27
+ }
28
+
29
+ type MarkedRawApiResponse = RawApiResponse & {
30
+ readonly [RAW_API_RESPONSE]: true;
31
+ };
21
32
 
22
- logger.debug({ statusCode }, "response");
33
+ export const rawApiResponse = (
34
+ response: RawApiResponse,
35
+ ): MarkedRawApiResponse =>
36
+ Object.assign(response, { [RAW_API_RESPONSE]: true as const });
23
37
 
38
+ const isRawApiResponse = (
39
+ body: Record<string, unknown>,
40
+ ): body is MarkedRawApiResponse & Record<string, unknown> =>
41
+ RAW_API_RESPONSE in body;
42
+
43
+ /**
44
+ * The headers every response carries whatever its body is: the CORS grant the
45
+ * browser needs, and the id the request's log lines are already tagged with.
46
+ * Returning that id is what makes a bug report's "correlation id" resolve to a
47
+ * server-side line; the browser can only read a non-safelisted header when it
48
+ * is exposed.
49
+ */
50
+ const envelopeHeaders = (): Record<string, string> => {
24
51
  const allowOrigin = resolveAllowedOrigin(getRequestOrigin());
25
52
 
26
- const corsHeaders: Record<string, string> = {
53
+ const headers: Record<string, string> = {
27
54
  "Access-Control-Allow-Origin": allowOrigin,
28
55
  "Access-Control-Allow-Headers": "Authorization,Content-Type",
29
56
  "Access-Control-Allow-Methods": "GET,POST,PUT,PATCH,DELETE,OPTIONS",
@@ -31,26 +58,46 @@ export const formatResponse = (
31
58
  };
32
59
 
33
60
  if (allowOrigin !== "*") {
34
- corsHeaders["Access-Control-Allow-Credentials"] = "true";
61
+ headers["Access-Control-Allow-Credentials"] = "true";
35
62
  }
36
63
 
37
- // The id the request's log lines are already tagged with. Returning it is
38
- // what makes a bug report's "correlation id" resolve to a server-side line;
39
- // the browser can only read a non-safelisted header when it is exposed.
40
64
  const correlationId = getRequestCorrelationId();
41
- const correlationHeaders: Record<string, string> = correlationId
42
- ? {
43
- "x-correlation-id": correlationId,
44
- "Access-Control-Expose-Headers": "x-correlation-id",
45
- }
46
- : {};
65
+ if (correlationId) {
66
+ headers["x-correlation-id"] = correlationId;
67
+ headers["Access-Control-Expose-Headers"] = "x-correlation-id";
68
+ }
69
+
70
+ return headers;
71
+ };
72
+
73
+ export const formatResponse = (
74
+ body: Record<string, unknown>,
75
+ statusCode = 200,
76
+ ): APIGatewayProxyResult => {
77
+ if (body.statusCode && typeof body.statusCode === "number") {
78
+ statusCode = body.statusCode;
79
+ }
80
+
81
+ if (isRawApiResponse(body)) {
82
+ logger.debug({ statusCode: body.statusCode }, "response");
83
+ return {
84
+ statusCode: body.statusCode,
85
+ headers: { ...envelopeHeaders(), ...body.headers },
86
+ body: body.body,
87
+ };
88
+ }
89
+
90
+ if ("body" in body && body.body && typeof body.body === "object") {
91
+ body = body.body as Record<string, unknown>;
92
+ }
93
+
94
+ logger.debug({ statusCode }, "response");
47
95
 
48
96
  return {
49
97
  statusCode: statusCode,
50
98
  headers: {
51
99
  "Content-Type": "application/json",
52
- ...corsHeaders,
53
- ...correlationHeaders,
100
+ ...envelopeHeaders(),
54
101
  },
55
102
  body: JSON.stringify(body),
56
103
  };
@@ -6,6 +6,7 @@ import {
6
6
  AddressRepo,
7
7
  CalendarCollectionRepo,
8
8
  CalendarEventIndexRepo,
9
+ CalendarFeedTokenRepo,
9
10
  CalendarObjectRepo,
10
11
  CalendarSuggestionRepo,
11
12
  ConfigImportRepo,
@@ -86,6 +87,7 @@ export const buildSqliteClient = async (): Promise<RemitClient> => {
86
87
  calendarCollection: new CalendarCollectionRepo(genericDb),
87
88
  calendarObject: new CalendarObjectRepo(genericDb),
88
89
  calendarEventIndex: new CalendarEventIndexRepo(genericDb),
90
+ calendarFeedToken: new CalendarFeedTokenRepo(genericDb),
89
91
  calendarSuggestion: new CalendarSuggestionRepo(genericDb),
90
92
  calendarUnitOfWork: new DrizzleCalendarUnitOfWork(genericDb),
91
93
  unitOfWork: new DrizzleUnitOfWork(messageDataDb),
@@ -6,6 +6,7 @@ import type {
6
6
  IAddressRepository,
7
7
  ICalendarCollectionRepository,
8
8
  ICalendarEventIndexRepository,
9
+ ICalendarFeedTokenRepository,
9
10
  ICalendarObjectRepository,
10
11
  ICalendarSuggestionRepository,
11
12
  ICalendarUnitOfWork,
@@ -130,6 +131,7 @@ export interface RemitClient {
130
131
  calendarCollection: ICalendarCollectionRepository;
131
132
  calendarObject: ICalendarObjectRepository;
132
133
  calendarEventIndex: ICalendarEventIndexRepository;
134
+ calendarFeedToken: ICalendarFeedTokenRepository;
133
135
  calendarSuggestion: ICalendarSuggestionRepository;
134
136
  calendarUnitOfWork: ICalendarUnitOfWork;
135
137
 
@@ -225,6 +227,7 @@ export interface RemitClientRepositories {
225
227
  calendarCollection: ICalendarCollectionRepository;
226
228
  calendarObject: ICalendarObjectRepository;
227
229
  calendarEventIndex: ICalendarEventIndexRepository;
230
+ calendarFeedToken: ICalendarFeedTokenRepository;
228
231
  calendarSuggestion: ICalendarSuggestionRepository;
229
232
  calendarUnitOfWork: ICalendarUnitOfWork;
230
233
  unitOfWork?: IUnitOfWork;
@@ -466,6 +469,7 @@ export const createRemitClient = (deps: RemitClientDeps): RemitClient => {
466
469
  calendarCollection: repositories.calendarCollection,
467
470
  calendarObject: repositories.calendarObject,
468
471
  calendarEventIndex: repositories.calendarEventIndex,
472
+ calendarFeedToken: repositories.calendarFeedToken,
469
473
  calendarSuggestion: repositories.calendarSuggestion,
470
474
  calendarUnitOfWork: repositories.calendarUnitOfWork,
471
475
  unitOfWork: repositories.unitOfWork,
package/src/types.ts CHANGED
@@ -63,6 +63,10 @@ export type OperationIds =
63
63
  | "CalendarDetailOperations_getCalendar"
64
64
  | "CalendarDetailOperations_updateCalendar"
65
65
  | "CalendarDetailOperations_deleteCalendar"
66
+ | "CalendarDetailOperations_getCalendarFeed"
67
+ | "CalendarDetailOperations_putCalendarFeed"
68
+ | "CalendarDetailOperations_revokeCalendarFeed"
69
+ | "CalendarFeedOperations_getCalendarFeedIcal"
66
70
  | "CalendarEventOperations_listCalendarEvents"
67
71
  | "CalendarEventOperations_createCalendarEvent"
68
72
  | "CalendarEventDetailOperations_getCalendarEvent"
@@ -180,6 +184,26 @@ export type CalendarDetailOperationIds = MatchPrefix<
180
184
  OperationIds
181
185
  >;
182
186
 
187
+ /**
188
+ * The feed sub-resource of `/calendars/{calendarId}`, handled beside the
189
+ * collection itself rather than inside it: minting a credential and serving one
190
+ * calendar's settings are different jobs on the same path.
191
+ */
192
+ export type CalendarFeedDetailOperationIds =
193
+ | "CalendarDetailOperations_getCalendarFeed"
194
+ | "CalendarDetailOperations_putCalendarFeed"
195
+ | "CalendarDetailOperations_revokeCalendarFeed";
196
+
197
+ export type CalendarCollectionDetailOperationIds = Exclude<
198
+ CalendarDetailOperationIds,
199
+ CalendarFeedDetailOperationIds
200
+ >;
201
+
202
+ export type CalendarFeedOperationIds = MatchPrefix<
203
+ "CalendarFeedOperations_",
204
+ OperationIds
205
+ >;
206
+
183
207
  export type CalendarEventOperationIds = MatchPrefix<
184
208
  "CalendarEventOperations_",
185
209
  OperationIds