@izak0s/spacebring-api 1.0.0 → 1.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.
package/README.md CHANGED
@@ -46,26 +46,38 @@ const sb = new Spacebring({
46
46
  // fetch: customFetch, // inject your own fetch (tests, proxies)
47
47
  });
48
48
 
49
- // Single-property envelopes are unwrapped: entities and plain arrays come back directly
50
- const invoice = await sb.billing.invoices.get(invoiceId);
51
- const locations = await sb.locations.list();
52
- await sb.visitors.visits.checkIn({ locationRef, visitRef });
53
-
54
- // Paginated lists return the page envelope, so nextPageToken stays available
55
- const { benefits, nextPageToken } = await sb.benefits.list({ locationRef });
49
+ async function main() {
50
+ // Single-property envelopes are unwrapped: entities and plain arrays come back directly
51
+ const invoice = await sb.billing.invoices.get(invoiceId);
52
+ const locations = await sb.locations.list();
53
+ await sb.visitors.visits.checkIn({ locationRef, visitRef });
54
+
55
+ // Paginated lists return the page envelope, so nextPageToken stays available
56
+ const { benefits, nextPageToken } = await sb.benefits.list({ locationRef });
57
+
58
+ // ...or let iterate() walk nextPageToken for you; breaking early stops fetching
59
+ for await (const booking of sb.resources.bookings.iterate({ locationRef })) {
60
+ console.log(booking.id);
61
+ }
56
62
 
57
- // ...or let iterate() walk nextPageToken for you; breaking early stops fetching
58
- for await (const booking of sb.resources.bookings.iterate({ locationRef })) {
59
- console.log(booking.id);
63
+ // Endpoints returning multiple payloads keep the envelope
64
+ const { invoice: paid, payment } = await sb.billing.invoices.pay(invoiceId, {
65
+ paymentMethod: { type: "stripe" },
66
+ });
60
67
  }
61
68
 
62
- // Endpoints returning multiple payloads keep the envelope
63
- const { invoice: paid, payment } = await sb.billing.invoices.pay(invoiceId, {
64
- paymentMethod: { type: "stripe" },
65
- });
69
+ main().catch(console.error);
66
70
  ```
67
71
 
68
- TypeScript helpers are exported too: `SpacebringConfig`, `SpacebringResources`, and the raw spec types `paths` / `components` / `operations` (e.g. `components["schemas"]["invoice"]`).
72
+ Entity types are exported by name — `import type { Booking, Invoice, Membership } from "@izak0s/spacebring-api"` — matching what the methods return (`get`/`create`/`update` resolve to the entity, `iterate()` yields it). Lower-level helpers too: `SpacebringConfig`, `SpacebringResources`, and the raw spec types `paths` / `components` / `operations`.
73
+
74
+ ### Data formats
75
+
76
+ Values are passed through exactly as the API sends them — no runtime conversion:
77
+
78
+ - **Dates** (`createDate`, `startDate`, …) are ISO 8601 strings — wrap in `new Date(booking.startDate)` when you need a `Date`.
79
+ - **Money** (`amount`, `price`, …) arrives as decimal floats. Fine for display; for accounting arithmetic convert to integer cents first to avoid floating-point drift.
80
+ - **IDs** (`id`, `*Ref`) are UUID strings.
69
81
 
70
82
  ---
71
83
 
package/dist/index.cjs CHANGED
@@ -40,13 +40,14 @@ function createClient(clientOptions) {
40
40
  fetch: baseFetch = globalThis.fetch,
41
41
  querySerializer: globalQuerySerializer,
42
42
  bodySerializer: globalBodySerializer,
43
+ pathSerializer: globalPathSerializer,
43
44
  headers: baseHeaders,
44
45
  requestInitExt = void 0,
45
46
  ...baseOptions
46
47
  } = { ...clientOptions };
47
48
  requestInitExt = supportsRequestInitExt() ? requestInitExt : void 0;
48
49
  baseUrl = removeTrailingSlash(baseUrl);
49
- const middlewares = [];
50
+ const globalMiddlewares = [];
50
51
  async function coreFetch(schemaPath, fetchOptions) {
51
52
  const {
52
53
  baseUrl: localBaseUrl,
@@ -57,7 +58,9 @@ function createClient(clientOptions) {
57
58
  parseAs = "json",
58
59
  querySerializer: requestQuerySerializer,
59
60
  bodySerializer = globalBodySerializer ?? defaultBodySerializer,
61
+ pathSerializer: requestPathSerializer,
60
62
  body,
63
+ middleware: requestMiddlewares = [],
61
64
  ...init
62
65
  } = fetchOptions || {};
63
66
  let finalBaseUrl = baseUrl;
@@ -71,6 +74,7 @@ function createClient(clientOptions) {
71
74
  ...requestQuerySerializer
72
75
  });
73
76
  }
77
+ const pathSerializer = requestPathSerializer || globalPathSerializer || defaultPathSerializer;
74
78
  const serializedBody = body === void 0 ? void 0 : bodySerializer(
75
79
  body,
76
80
  // Note: we declare mergeHeaders() both here and below because it’s a bit of a chicken-or-egg situation:
@@ -90,6 +94,7 @@ function createClient(clientOptions) {
90
94
  headers,
91
95
  params.header
92
96
  );
97
+ const finalMiddlewares = [...globalMiddlewares, ...requestMiddlewares];
93
98
  const requestInit = {
94
99
  redirect: "follow",
95
100
  ...baseOptions,
@@ -100,7 +105,7 @@ function createClient(clientOptions) {
100
105
  let id;
101
106
  let options;
102
107
  let request = new Request(
103
- createFinalURL(schemaPath, { baseUrl: finalBaseUrl, params, querySerializer }),
108
+ createFinalURL(schemaPath, { baseUrl: finalBaseUrl, params, querySerializer, pathSerializer }),
104
109
  requestInit
105
110
  );
106
111
  let response;
@@ -109,16 +114,17 @@ function createClient(clientOptions) {
109
114
  request[key] = init[key];
110
115
  }
111
116
  }
112
- if (middlewares.length) {
117
+ if (finalMiddlewares.length) {
113
118
  id = randomID();
114
119
  options = Object.freeze({
115
120
  baseUrl: finalBaseUrl,
116
121
  fetch,
117
122
  parseAs,
118
123
  querySerializer,
119
- bodySerializer
124
+ bodySerializer,
125
+ pathSerializer
120
126
  });
121
- for (const m of middlewares) {
127
+ for (const m of finalMiddlewares) {
122
128
  if (m && typeof m === "object" && typeof m.onRequest === "function") {
123
129
  const result = await m.onRequest({
124
130
  request,
@@ -145,9 +151,9 @@ function createClient(clientOptions) {
145
151
  response = await fetch(request, requestInitExt);
146
152
  } catch (error2) {
147
153
  let errorAfterMiddleware = error2;
148
- if (middlewares.length) {
149
- for (let i = middlewares.length - 1; i >= 0; i--) {
150
- const m = middlewares[i];
154
+ if (finalMiddlewares.length) {
155
+ for (let i = finalMiddlewares.length - 1; i >= 0; i--) {
156
+ const m = finalMiddlewares[i];
151
157
  if (m && typeof m === "object" && typeof m.onError === "function") {
152
158
  const result = await m.onError({
153
159
  request,
@@ -176,9 +182,9 @@ function createClient(clientOptions) {
176
182
  throw errorAfterMiddleware;
177
183
  }
178
184
  }
179
- if (middlewares.length) {
180
- for (let i = middlewares.length - 1; i >= 0; i--) {
181
- const m = middlewares[i];
185
+ if (finalMiddlewares.length) {
186
+ for (let i = finalMiddlewares.length - 1; i >= 0; i--) {
187
+ const m = finalMiddlewares[i];
182
188
  if (m && typeof m === "object" && typeof m.onResponse === "function") {
183
189
  const result = await m.onResponse({
184
190
  request,
@@ -198,14 +204,22 @@ function createClient(clientOptions) {
198
204
  }
199
205
  }
200
206
  }
201
- if (response.status === 204 || request.method === "HEAD" || response.headers.get("Content-Length") === "0") {
207
+ const contentLength = response.headers.get("Content-Length");
208
+ if (response.status === 204 || request.method === "HEAD" || contentLength === "0" && !response.headers.get("Transfer-Encoding")?.includes("chunked")) {
202
209
  return response.ok ? { data: void 0, response } : { error: void 0, response };
203
210
  }
204
211
  if (response.ok) {
205
- if (parseAs === "stream") {
206
- return { data: response.body, response };
207
- }
208
- return { data: await response[parseAs](), response };
212
+ const getResponseData = async () => {
213
+ if (parseAs === "stream") {
214
+ return response.body;
215
+ }
216
+ if (parseAs === "json" && !contentLength) {
217
+ const raw = await response.text();
218
+ return raw ? JSON.parse(raw) : void 0;
219
+ }
220
+ return await response[parseAs]();
221
+ };
222
+ return { data: await getResponseData(), response };
209
223
  }
210
224
  let error = await response.text();
211
225
  try {
@@ -259,15 +273,15 @@ function createClient(clientOptions) {
259
273
  if (typeof m !== "object" || !("onRequest" in m || "onResponse" in m || "onError" in m)) {
260
274
  throw new Error("Middleware must be an object with one of `onRequest()`, `onResponse() or `onError()`");
261
275
  }
262
- middlewares.push(m);
276
+ globalMiddlewares.push(m);
263
277
  }
264
278
  },
265
279
  /** Unregister middleware */
266
280
  eject(...middleware) {
267
281
  for (const m of middleware) {
268
- const i = middlewares.indexOf(m);
282
+ const i = globalMiddlewares.indexOf(m);
269
283
  if (i !== -1) {
270
- middlewares.splice(i, 1);
284
+ globalMiddlewares.splice(i, 1);
271
285
  }
272
286
  }
273
287
  }
@@ -448,7 +462,7 @@ function defaultBodySerializer(body, headers) {
448
462
  function createFinalURL(pathname, options) {
449
463
  let finalURL = `${options.baseUrl}${pathname}`;
450
464
  if (options.params?.path) {
451
- finalURL = defaultPathSerializer(finalURL, options.params.path);
465
+ finalURL = options.pathSerializer(finalURL, options.params.path);
452
466
  }
453
467
  let search = options.querySerializer(options.params.query ?? {});
454
468
  if (search.startsWith("?")) {