@medalsocial/sdk 1.7.0 → 1.9.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.
@@ -35,17 +35,21 @@ var BaseClient = class {
35
35
  this.config = config;
36
36
  }
37
37
  /** Execute an authenticated GET request and return the parsed JSON body. */
38
- async get(path, params) {
38
+ async get(path, params, options) {
39
39
  const url = this.buildUrl(path, params);
40
- return this.request(url, { method: "GET" });
40
+ return this.request(url, { method: "GET", headers: options?.headers });
41
41
  }
42
42
  /** Execute an authenticated POST request with a JSON body. */
43
43
  async post(path, body, options) {
44
- return this.request(this.buildUrl(path), {
45
- method: "POST",
46
- headers: this.writeHeaders(options),
47
- body: body !== void 0 ? JSON.stringify(body) : void 0
48
- });
44
+ return this.request(
45
+ this.buildUrl(path),
46
+ {
47
+ method: "POST",
48
+ headers: this.writeHeaders(options),
49
+ body: body !== void 0 ? JSON.stringify(body) : void 0
50
+ },
51
+ options?.retry
52
+ );
49
53
  }
50
54
  /**
51
55
  * Execute a POST that must never execute twice, guaranteeing an
@@ -72,21 +76,33 @@ var BaseClient = class {
72
76
  }
73
77
  /** Execute an authenticated PATCH request with a JSON body. */
74
78
  async patch(path, body, options) {
75
- return this.request(this.buildUrl(path), {
76
- method: "PATCH",
77
- headers: this.writeHeaders(options),
78
- body: JSON.stringify(body)
79
- });
79
+ return this.request(
80
+ this.buildUrl(path),
81
+ {
82
+ method: "PATCH",
83
+ headers: this.writeHeaders(options),
84
+ body: JSON.stringify(body)
85
+ },
86
+ options?.retry
87
+ );
80
88
  }
81
89
  /** Execute an authenticated DELETE request. */
82
90
  async delete(path, options) {
83
- return this.request(this.buildUrl(path), {
84
- method: "DELETE",
85
- headers: this.writeHeaders(options)
86
- });
91
+ return this.request(
92
+ this.buildUrl(path),
93
+ {
94
+ method: "DELETE",
95
+ headers: this.writeHeaders(options)
96
+ },
97
+ options?.retry
98
+ );
87
99
  }
88
100
  writeHeaders(options) {
89
- const headers = { "content-type": "application/json" };
101
+ const headers = {};
102
+ for (const [key, value] of Object.entries(options?.headers ?? {})) {
103
+ headers[key.toLowerCase()] = value;
104
+ }
105
+ headers["content-type"] = "application/json";
90
106
  if (options?.idempotencyKey) {
91
107
  headers["idempotency-key"] = options.idempotencyKey;
92
108
  }
@@ -106,8 +122,8 @@ var BaseClient = class {
106
122
  }
107
123
  return url.toString();
108
124
  }
109
- async request(url, init) {
110
- const maxAttempts = 3;
125
+ async request(url, init, retry = true) {
126
+ const maxAttempts = retry ? 3 : 1;
111
127
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
112
128
  const headers = new Headers(init.headers);
113
129
  headers.set("authorization", `Bearer ${this.config.token}`);
@@ -308,14 +324,75 @@ var BookingsManage = class {
308
324
  );
309
325
  }
310
326
  };
327
+ var BookingsPersons = class {
328
+ constructor(client) {
329
+ this.client = client;
330
+ }
331
+ client;
332
+ /** Persons a contact books for. Active-only unless `include_inactive`. */
333
+ async list(contactId, options) {
334
+ const params = { contact_id: contactId };
335
+ if (options?.include_inactive !== void 0) {
336
+ params.include_inactive = String(options.include_inactive);
337
+ }
338
+ return this.client.get("/api/v1/bookings/persons", params);
339
+ }
340
+ /** Add a person under a contact. */
341
+ async create(input, options) {
342
+ return this.client.postOnce("/api/v1/bookings/persons", input, options);
343
+ }
344
+ };
345
+ var BookingsRelations = class {
346
+ constructor(client) {
347
+ this.client = client;
348
+ }
349
+ client;
350
+ /** Relations a contact holds, split into outgoing and incoming. */
351
+ async list(contactId) {
352
+ return this.client.get("/api/v1/bookings/relations", { contact_id: contactId });
353
+ }
354
+ /** Create a relation from one contact to another. */
355
+ async create(input, options) {
356
+ return this.client.postOnce("/api/v1/bookings/relations", input, options);
357
+ }
358
+ };
359
+ var BookingsEvents = class {
360
+ constructor(client) {
361
+ this.client = client;
362
+ }
363
+ client;
364
+ /** Arrangementer in a date range (`yyyy-mm-dd`, inclusive). */
365
+ async list(options) {
366
+ const params = { from: options.from, to: options.to };
367
+ if (options.status) params.status = options.status;
368
+ return this.client.get("/api/v1/bookings/events", params);
369
+ }
370
+ /** Get an arrangement by ID. */
371
+ async get(id) {
372
+ return this.client.get(`/api/v1/bookings/events/${encodeURIComponent(id)}`);
373
+ }
374
+ /** Create an arrangement from a template. */
375
+ async create(input, options) {
376
+ return this.client.postOnce("/api/v1/bookings/events", input, options);
377
+ }
378
+ };
311
379
  var Bookings = class {
312
380
  constructor(client) {
313
381
  this.client = client;
314
382
  this.manage = new BookingsManage(client);
383
+ this.persons = new BookingsPersons(client);
384
+ this.relations = new BookingsRelations(client);
385
+ this.events = new BookingsEvents(client);
315
386
  }
316
387
  client;
317
388
  /** Customer-side actions addressed by manage token. */
318
389
  manage;
390
+ /** Persons a contact books for — children, pets, employees. */
391
+ persons;
392
+ /** Directional relations between contacts. */
393
+ relations;
394
+ /** Arrangementer — scheduled group sessions bookings register against. */
395
+ events;
319
396
  /** List the bookable service catalogue. Active-only unless asked otherwise. */
320
397
  async listServices(options) {
321
398
  const params = {};
@@ -881,6 +958,111 @@ var Helpdesk = class {
881
958
  }
882
959
  };
883
960
 
961
+ // src/resources/portal.ts
962
+ function withSession(session) {
963
+ return { headers: { "x-portal-session": session } };
964
+ }
965
+ var ONCE = { retry: false };
966
+ var PortalLogin = class {
967
+ constructor(client) {
968
+ this.client = client;
969
+ }
970
+ client;
971
+ /**
972
+ * E-mail a one-time code to the address.
973
+ *
974
+ * Always 202 `{ status: "sent" }` — enumeration-safe: `"sent"` does not
975
+ * confirm that the address belongs to a contact. Rate-limited per address
976
+ * and per caller (`429 RATE_LIMITED`).
977
+ */
978
+ async start(input) {
979
+ return this.client.post("/api/v1/portal/login/start", input);
980
+ }
981
+ /**
982
+ * Exchange the e-mailed code for a session.
983
+ *
984
+ * `session_token` is a bearer credential for ONE contact — keep it in an
985
+ * HttpOnly cookie on the site's server. A wrong, burned or expired code all
986
+ * answer `401 PORTAL_CODE_INVALID`; the three are not distinguished, so the
987
+ * response is not an oracle for which codes exist.
988
+ */
989
+ async verify(input) {
990
+ return this.client.post("/api/v1/portal/login/verify", input, ONCE);
991
+ }
992
+ };
993
+ var Portal = class {
994
+ constructor(client) {
995
+ this.client = client;
996
+ this.login = new PortalLogin(client);
997
+ }
998
+ client;
999
+ /** E-mail one-time-code login: `start` sends the code, `verify` exchanges it. */
1000
+ login;
1001
+ /**
1002
+ * Revoke the session. Resolves to `undefined` (the route answers 204).
1003
+ *
1004
+ * Not keyed: revoking twice reaches the same state — the second call answers
1005
+ * `401 PORTAL_SESSION_INVALID`, which is the outcome you wanted anyway.
1006
+ */
1007
+ async logout(session) {
1008
+ await this.client.post("/api/v1/portal/logout", void 0, {
1009
+ ...withSession(session),
1010
+ ...ONCE
1011
+ });
1012
+ }
1013
+ /** The signed-in contact's own profile. */
1014
+ async me(session) {
1015
+ return this.client.get("/api/v1/portal/me", void 0, withSession(session));
1016
+ }
1017
+ /**
1018
+ * Update the signed-in contact's profile. Only the supplied fields change;
1019
+ * `phone: null` clears the number and `family` replaces the whole list.
1020
+ * `marketing_consent` records a `marketing_email` consent decision with
1021
+ * source `portal`. Returns the profile as it is after the change.
1022
+ */
1023
+ /**
1024
+ * A profile patch is not idempotency-keyed on the server and a `marketing_consent`
1025
+ * change records a dated consent event, so a retry after a committed-but-lost
1026
+ * response would repeat that event: sent exactly once, like the other writes.
1027
+ */
1028
+ async updateMe(session, patch) {
1029
+ return this.client.patch("/api/v1/portal/me", patch, { ...withSession(session), ...ONCE });
1030
+ }
1031
+ /**
1032
+ * The contact's bookings split into `upcoming` and `past`. An upcoming
1033
+ * booking that is still inside the workspace's policy windows carries
1034
+ * `manage_token` and `can_manage: true`; use the token to open the site's
1035
+ * manage page (`medal.bookings.manage.*`).
1036
+ */
1037
+ async myBookings(session) {
1038
+ return this.client.get("/api/v1/portal/me/bookings", void 0, withSession(session));
1039
+ }
1040
+ /**
1041
+ * Everything the workspace holds about the contact — profile, family,
1042
+ * consents and bookings — as one JSON document (GDPR Art. 15). Synchronous,
1043
+ * unlike `medal.gdpr.requestExport()`, which exports the whole workspace.
1044
+ *
1045
+ * Not keyed: a read-only snapshot, so a retried call costs nothing and
1046
+ * duplicates nothing.
1047
+ */
1048
+ async exportMyData(session) {
1049
+ return this.client.post("/api/v1/portal/me/export", void 0, withSession(session));
1050
+ }
1051
+ /**
1052
+ * Erase the contact (GDPR Art. 17). Resolves to `undefined` (the route
1053
+ * answers 204); the session is revoked as part of the deletion.
1054
+ *
1055
+ * Not keyed: deletion is terminal, so a retry meets a revoked session and
1056
+ * answers `401 PORTAL_SESSION_INVALID` rather than deleting anything else.
1057
+ */
1058
+ async deleteMe(session) {
1059
+ await this.client.post("/api/v1/portal/me/delete", void 0, {
1060
+ ...withSession(session),
1061
+ ...ONCE
1062
+ });
1063
+ }
1064
+ };
1065
+
884
1066
  // src/resources/posts.ts
885
1067
  var Posts = class {
886
1068
  constructor(client) {
@@ -1186,6 +1368,7 @@ var Medal = class {
1186
1368
  deals;
1187
1369
  gdpr;
1188
1370
  helpdesk;
1371
+ portal;
1189
1372
  posts;
1190
1373
  scan;
1191
1374
  webhooks;
@@ -1215,6 +1398,7 @@ var Medal = class {
1215
1398
  this.deals = new Deals(client);
1216
1399
  this.gdpr = new Gdpr(client);
1217
1400
  this.helpdesk = new Helpdesk(client, confirmer);
1401
+ this.portal = new Portal(client);
1218
1402
  this.posts = new Posts(client);
1219
1403
  this.scan = new Scan(client);
1220
1404
  this.webhooks = new Webhooks(client, confirmer);
@@ -1241,6 +1425,7 @@ export {
1241
1425
  Helpdesk,
1242
1426
  Medal,
1243
1427
  MedalApiError,
1428
+ Portal,
1244
1429
  Posts,
1245
1430
  Scan,
1246
1431
  WebhookVerificationError,