@dyrected/sdk 0.0.1 → 1.0.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/dist/index.js CHANGED
@@ -1,448 +1,6 @@
1
1
  // src/index.ts
2
2
  import qs from "qs";
3
3
 
4
- // ../../node_modules/.pnpm/hono@4.12.18/node_modules/hono/dist/request/constants.js
5
- var GET_MATCH_RESULT = /* @__PURE__ */ Symbol();
6
-
7
- // ../../node_modules/.pnpm/hono@4.12.18/node_modules/hono/dist/utils/body.js
8
- var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
9
- const { all = false, dot = false } = options;
10
- const headers = request instanceof HonoRequest ? request.raw.headers : request.headers;
11
- const contentType = headers.get("Content-Type");
12
- if (contentType?.startsWith("multipart/form-data") || contentType?.startsWith("application/x-www-form-urlencoded")) {
13
- return parseFormData(request, { all, dot });
14
- }
15
- return {};
16
- };
17
- async function parseFormData(request, options) {
18
- const formData = await request.formData();
19
- if (formData) {
20
- return convertFormDataToBodyData(formData, options);
21
- }
22
- return {};
23
- }
24
- function convertFormDataToBodyData(formData, options) {
25
- const form = /* @__PURE__ */ Object.create(null);
26
- formData.forEach((value, key) => {
27
- const shouldParseAllValues = options.all || key.endsWith("[]");
28
- if (!shouldParseAllValues) {
29
- form[key] = value;
30
- } else {
31
- handleParsingAllValues(form, key, value);
32
- }
33
- });
34
- if (options.dot) {
35
- Object.entries(form).forEach(([key, value]) => {
36
- const shouldParseDotValues = key.includes(".");
37
- if (shouldParseDotValues) {
38
- handleParsingNestedValues(form, key, value);
39
- delete form[key];
40
- }
41
- });
42
- }
43
- return form;
44
- }
45
- var handleParsingAllValues = (form, key, value) => {
46
- if (form[key] !== void 0) {
47
- if (Array.isArray(form[key])) {
48
- ;
49
- form[key].push(value);
50
- } else {
51
- form[key] = [form[key], value];
52
- }
53
- } else {
54
- if (!key.endsWith("[]")) {
55
- form[key] = value;
56
- } else {
57
- form[key] = [value];
58
- }
59
- }
60
- };
61
- var handleParsingNestedValues = (form, key, value) => {
62
- if (/(?:^|\.)__proto__\./.test(key)) {
63
- return;
64
- }
65
- let nestedForm = form;
66
- const keys = key.split(".");
67
- keys.forEach((key2, index) => {
68
- if (index === keys.length - 1) {
69
- nestedForm[key2] = value;
70
- } else {
71
- if (!nestedForm[key2] || typeof nestedForm[key2] !== "object" || Array.isArray(nestedForm[key2]) || nestedForm[key2] instanceof File) {
72
- nestedForm[key2] = /* @__PURE__ */ Object.create(null);
73
- }
74
- nestedForm = nestedForm[key2];
75
- }
76
- });
77
- };
78
-
79
- // ../../node_modules/.pnpm/hono@4.12.18/node_modules/hono/dist/utils/url.js
80
- var tryDecode = (str, decoder) => {
81
- try {
82
- return decoder(str);
83
- } catch {
84
- return str.replace(/(?:%[0-9A-Fa-f]{2})+/g, (match2) => {
85
- try {
86
- return decoder(match2);
87
- } catch {
88
- return match2;
89
- }
90
- });
91
- }
92
- };
93
- var _decodeURI = (value) => {
94
- if (!/[%+]/.test(value)) {
95
- return value;
96
- }
97
- if (value.indexOf("+") !== -1) {
98
- value = value.replace(/\+/g, " ");
99
- }
100
- return value.indexOf("%") !== -1 ? tryDecode(value, decodeURIComponent_) : value;
101
- };
102
- var _getQueryParam = (url, key, multiple) => {
103
- let encoded;
104
- if (!multiple && key && !/[%+]/.test(key)) {
105
- let keyIndex2 = url.indexOf("?", 8);
106
- if (keyIndex2 === -1) {
107
- return void 0;
108
- }
109
- if (!url.startsWith(key, keyIndex2 + 1)) {
110
- keyIndex2 = url.indexOf(`&${key}`, keyIndex2 + 1);
111
- }
112
- while (keyIndex2 !== -1) {
113
- const trailingKeyCode = url.charCodeAt(keyIndex2 + key.length + 1);
114
- if (trailingKeyCode === 61) {
115
- const valueIndex = keyIndex2 + key.length + 2;
116
- const endIndex = url.indexOf("&", valueIndex);
117
- return _decodeURI(url.slice(valueIndex, endIndex === -1 ? void 0 : endIndex));
118
- } else if (trailingKeyCode == 38 || isNaN(trailingKeyCode)) {
119
- return "";
120
- }
121
- keyIndex2 = url.indexOf(`&${key}`, keyIndex2 + 1);
122
- }
123
- encoded = /[%+]/.test(url);
124
- if (!encoded) {
125
- return void 0;
126
- }
127
- }
128
- const results = {};
129
- encoded ??= /[%+]/.test(url);
130
- let keyIndex = url.indexOf("?", 8);
131
- while (keyIndex !== -1) {
132
- const nextKeyIndex = url.indexOf("&", keyIndex + 1);
133
- let valueIndex = url.indexOf("=", keyIndex);
134
- if (valueIndex > nextKeyIndex && nextKeyIndex !== -1) {
135
- valueIndex = -1;
136
- }
137
- let name = url.slice(
138
- keyIndex + 1,
139
- valueIndex === -1 ? nextKeyIndex === -1 ? void 0 : nextKeyIndex : valueIndex
140
- );
141
- if (encoded) {
142
- name = _decodeURI(name);
143
- }
144
- keyIndex = nextKeyIndex;
145
- if (name === "") {
146
- continue;
147
- }
148
- let value;
149
- if (valueIndex === -1) {
150
- value = "";
151
- } else {
152
- value = url.slice(valueIndex + 1, nextKeyIndex === -1 ? void 0 : nextKeyIndex);
153
- if (encoded) {
154
- value = _decodeURI(value);
155
- }
156
- }
157
- if (multiple) {
158
- if (!(results[name] && Array.isArray(results[name]))) {
159
- results[name] = [];
160
- }
161
- ;
162
- results[name].push(value);
163
- } else {
164
- results[name] ??= value;
165
- }
166
- }
167
- return key ? results[key] : results;
168
- };
169
- var getQueryParam = _getQueryParam;
170
- var getQueryParams = (url, key) => {
171
- return _getQueryParam(url, key, true);
172
- };
173
- var decodeURIComponent_ = decodeURIComponent;
174
-
175
- // ../../node_modules/.pnpm/hono@4.12.18/node_modules/hono/dist/request.js
176
- var tryDecodeURIComponent = (str) => tryDecode(str, decodeURIComponent_);
177
- var HonoRequest = class {
178
- /**
179
- * `.raw` can get the raw Request object.
180
- *
181
- * @see {@link https://hono.dev/docs/api/request#raw}
182
- *
183
- * @example
184
- * ```ts
185
- * // For Cloudflare Workers
186
- * app.post('/', async (c) => {
187
- * const metadata = c.req.raw.cf?.hostMetadata?
188
- * ...
189
- * })
190
- * ```
191
- */
192
- raw;
193
- #validatedData;
194
- // Short name of validatedData
195
- #matchResult;
196
- routeIndex = 0;
197
- /**
198
- * `.path` can get the pathname of the request.
199
- *
200
- * @see {@link https://hono.dev/docs/api/request#path}
201
- *
202
- * @example
203
- * ```ts
204
- * app.get('/about/me', (c) => {
205
- * const pathname = c.req.path // `/about/me`
206
- * })
207
- * ```
208
- */
209
- path;
210
- bodyCache = {};
211
- constructor(request, path = "/", matchResult = [[]]) {
212
- this.raw = request;
213
- this.path = path;
214
- this.#matchResult = matchResult;
215
- this.#validatedData = {};
216
- }
217
- param(key) {
218
- return key ? this.#getDecodedParam(key) : this.#getAllDecodedParams();
219
- }
220
- #getDecodedParam(key) {
221
- const paramKey = this.#matchResult[0][this.routeIndex][1][key];
222
- const param = this.#getParamValue(paramKey);
223
- return param && /\%/.test(param) ? tryDecodeURIComponent(param) : param;
224
- }
225
- #getAllDecodedParams() {
226
- const decoded = {};
227
- const keys = Object.keys(this.#matchResult[0][this.routeIndex][1]);
228
- for (const key of keys) {
229
- const value = this.#getParamValue(this.#matchResult[0][this.routeIndex][1][key]);
230
- if (value !== void 0) {
231
- decoded[key] = /\%/.test(value) ? tryDecodeURIComponent(value) : value;
232
- }
233
- }
234
- return decoded;
235
- }
236
- #getParamValue(paramKey) {
237
- return this.#matchResult[1] ? this.#matchResult[1][paramKey] : paramKey;
238
- }
239
- query(key) {
240
- return getQueryParam(this.url, key);
241
- }
242
- queries(key) {
243
- return getQueryParams(this.url, key);
244
- }
245
- header(name) {
246
- if (name) {
247
- return this.raw.headers.get(name) ?? void 0;
248
- }
249
- const headerData = {};
250
- this.raw.headers.forEach((value, key) => {
251
- headerData[key] = value;
252
- });
253
- return headerData;
254
- }
255
- async parseBody(options) {
256
- return parseBody(this, options);
257
- }
258
- #cachedBody = (key) => {
259
- const { bodyCache, raw } = this;
260
- const cachedBody = bodyCache[key];
261
- if (cachedBody) {
262
- return cachedBody;
263
- }
264
- const anyCachedKey = Object.keys(bodyCache)[0];
265
- if (anyCachedKey) {
266
- return bodyCache[anyCachedKey].then((body) => {
267
- if (anyCachedKey === "json") {
268
- body = JSON.stringify(body);
269
- }
270
- return new Response(body)[key]();
271
- });
272
- }
273
- return bodyCache[key] = raw[key]();
274
- };
275
- /**
276
- * `.json()` can parse Request body of type `application/json`
277
- *
278
- * @see {@link https://hono.dev/docs/api/request#json}
279
- *
280
- * @example
281
- * ```ts
282
- * app.post('/entry', async (c) => {
283
- * const body = await c.req.json()
284
- * })
285
- * ```
286
- */
287
- json() {
288
- return this.#cachedBody("text").then((text) => JSON.parse(text));
289
- }
290
- /**
291
- * `.text()` can parse Request body of type `text/plain`
292
- *
293
- * @see {@link https://hono.dev/docs/api/request#text}
294
- *
295
- * @example
296
- * ```ts
297
- * app.post('/entry', async (c) => {
298
- * const body = await c.req.text()
299
- * })
300
- * ```
301
- */
302
- text() {
303
- return this.#cachedBody("text");
304
- }
305
- /**
306
- * `.arrayBuffer()` parse Request body as an `ArrayBuffer`
307
- *
308
- * @see {@link https://hono.dev/docs/api/request#arraybuffer}
309
- *
310
- * @example
311
- * ```ts
312
- * app.post('/entry', async (c) => {
313
- * const body = await c.req.arrayBuffer()
314
- * })
315
- * ```
316
- */
317
- arrayBuffer() {
318
- return this.#cachedBody("arrayBuffer");
319
- }
320
- /**
321
- * Parses the request body as a `Blob`.
322
- * @example
323
- * ```ts
324
- * app.post('/entry', async (c) => {
325
- * const body = await c.req.blob();
326
- * });
327
- * ```
328
- * @see https://hono.dev/docs/api/request#blob
329
- */
330
- blob() {
331
- return this.#cachedBody("blob");
332
- }
333
- /**
334
- * Parses the request body as `FormData`.
335
- * @example
336
- * ```ts
337
- * app.post('/entry', async (c) => {
338
- * const body = await c.req.formData();
339
- * });
340
- * ```
341
- * @see https://hono.dev/docs/api/request#formdata
342
- */
343
- formData() {
344
- return this.#cachedBody("formData");
345
- }
346
- /**
347
- * Adds validated data to the request.
348
- *
349
- * @param target - The target of the validation.
350
- * @param data - The validated data to add.
351
- */
352
- addValidatedData(target, data) {
353
- this.#validatedData[target] = data;
354
- }
355
- valid(target) {
356
- return this.#validatedData[target];
357
- }
358
- /**
359
- * `.url()` can get the request url strings.
360
- *
361
- * @see {@link https://hono.dev/docs/api/request#url}
362
- *
363
- * @example
364
- * ```ts
365
- * app.get('/about/me', (c) => {
366
- * const url = c.req.url // `http://localhost:8787/about/me`
367
- * ...
368
- * })
369
- * ```
370
- */
371
- get url() {
372
- return this.raw.url;
373
- }
374
- /**
375
- * `.method()` can get the method name of the request.
376
- *
377
- * @see {@link https://hono.dev/docs/api/request#method}
378
- *
379
- * @example
380
- * ```ts
381
- * app.get('/about/me', (c) => {
382
- * const method = c.req.method // `GET`
383
- * })
384
- * ```
385
- */
386
- get method() {
387
- return this.raw.method;
388
- }
389
- get [GET_MATCH_RESULT]() {
390
- return this.#matchResult;
391
- }
392
- /**
393
- * `.matchedRoutes()` can return a matched route in the handler
394
- *
395
- * @deprecated
396
- *
397
- * Use matchedRoutes helper defined in "hono/route" instead.
398
- *
399
- * @see {@link https://hono.dev/docs/api/request#matchedroutes}
400
- *
401
- * @example
402
- * ```ts
403
- * app.use('*', async function logger(c, next) {
404
- * await next()
405
- * c.req.matchedRoutes.forEach(({ handler, method, path }, i) => {
406
- * const name = handler.name || (handler.length < 2 ? '[handler]' : '[middleware]')
407
- * console.log(
408
- * method,
409
- * ' ',
410
- * path,
411
- * ' '.repeat(Math.max(10 - path.length, 0)),
412
- * name,
413
- * i === c.req.routeIndex ? '<- respond from here' : ''
414
- * )
415
- * })
416
- * })
417
- * ```
418
- */
419
- get matchedRoutes() {
420
- return this.#matchResult[0].map(([[, route]]) => route);
421
- }
422
- /**
423
- * `routePath()` can retrieve the path registered within the handler
424
- *
425
- * @deprecated
426
- *
427
- * Use routePath helper defined in "hono/route" instead.
428
- *
429
- * @see {@link https://hono.dev/docs/api/request#routepath}
430
- *
431
- * @example
432
- * ```ts
433
- * app.get('/posts/:id', (c) => {
434
- * return c.json({ path: c.req.routePath })
435
- * })
436
- * ```
437
- */
438
- get routePath() {
439
- return this.#matchResult[0].map(([[, route]]) => route)[this.routeIndex].path;
440
- }
441
- };
442
-
443
- // ../../node_modules/.pnpm/hono@4.12.18/node_modules/hono/dist/router/reg-exp-router/node.js
444
- var regExpMetaChars = new Set(".\\+*[^]$()");
445
-
446
4
  // src/query-builder.ts
447
5
  var QueryBuilder = class {
448
6
  constructor(collection, executor) {
@@ -472,6 +30,10 @@ var QueryBuilder = class {
472
30
  this.args.depth = depth;
473
31
  return this;
474
32
  }
33
+ seed(data) {
34
+ this.args.initialData = data;
35
+ return this;
36
+ }
475
37
  async exec() {
476
38
  return this.executor(this.collection, this.args);
477
39
  }
@@ -482,28 +44,82 @@ var QueryBuilder = class {
482
44
  };
483
45
 
484
46
  // src/index.ts
47
+ var DyrectedError = class extends Error {
48
+ statusCode;
49
+ errors;
50
+ constructor(message, statusCode, errors = []) {
51
+ super(message);
52
+ this.name = "DyrectedError";
53
+ this.statusCode = statusCode;
54
+ this.errors = errors;
55
+ }
56
+ };
485
57
  var DyrectedClient = class {
486
58
  baseUrl;
487
59
  headers;
488
60
  fetch;
61
+ defaultDepth;
489
62
  constructor(config) {
490
63
  this.baseUrl = config.baseUrl.replace(/\/$/, "");
491
64
  this.fetch = config.fetch || fetch;
65
+ this.defaultDepth = config.defaultDepth ?? 1;
492
66
  this.headers = {
493
67
  "Content-Type": "application/json",
494
- ...config.apiKey ? { "X-API-Key": config.apiKey } : {},
68
+ ...config.apiKey ? { "x-api-key": config.apiKey } : {},
69
+ ...config.siteId ? { "x-site-id": config.siteId } : {},
495
70
  ...config.headers
496
71
  };
497
72
  }
73
+ /**
74
+ * Update the Authorization header with a Bearer token.
75
+ * Call this after a successful login.
76
+ */
77
+ setToken(token) {
78
+ this.headers["Authorization"] = `Bearer ${token}`;
79
+ }
80
+ /**
81
+ * Remove the Authorization header.
82
+ * Call this after logout.
83
+ */
84
+ clearToken() {
85
+ delete this.headers["Authorization"];
86
+ }
498
87
  getBaseUrl() {
499
88
  return this.baseUrl;
500
89
  }
501
90
  async getSchemas() {
502
91
  return this.request("/api/schemas");
503
92
  }
93
+ /**
94
+ * Fetch draft data for a specific preview token.
95
+ * Used in "token" preview mode.
96
+ */
97
+ async getPreviewData(token) {
98
+ return this.request(`/api/preview-data?token=${token}`);
99
+ }
504
100
  async find(collection, args = {}) {
505
- const query = qs.stringify(args, { addQueryPrefix: true });
101
+ const { initialData, ...queryArgs } = args;
102
+ const normalizedArgs = { ...queryArgs };
103
+ if (normalizedArgs.where && typeof normalizedArgs.where === "object") {
104
+ normalizedArgs.where = JSON.stringify(normalizedArgs.where);
105
+ }
106
+ const query = qs.stringify(normalizedArgs, { addQueryPrefix: true });
506
107
  const res = await this.request(`/api/collections/${collection}${query}`);
108
+ if (res.docs.length === 0 && initialData && initialData.length > 0) {
109
+ this.request(`/api/collections/${collection}/seed`, {
110
+ method: "POST",
111
+ body: JSON.stringify({ data: initialData })
112
+ }).catch((err) => console.error(`[dyrected/sdk] Failed to auto-seed collection "${collection}":`, err));
113
+ return {
114
+ docs: initialData,
115
+ total: initialData.length,
116
+ limit: initialData.length,
117
+ page: 1,
118
+ totalPages: 1,
119
+ hasNextPage: false,
120
+ hasPrevPage: false
121
+ };
122
+ }
507
123
  return res;
508
124
  }
509
125
  /**
@@ -519,18 +135,80 @@ var DyrectedClient = class {
519
135
  if (args.limit) qb.limit(args.limit);
520
136
  if (args.page) qb.page(args.page);
521
137
  if (args.depth) qb.depth(args.depth);
138
+ if (args.initialData) qb.seed(args.initialData);
522
139
  }
523
140
  return qb;
524
141
  },
525
142
  findOne: (id, args = {}) => this.findOne(slug, id, args),
526
143
  create: (data) => this.create(slug, data),
527
144
  update: (id, data) => this.update(slug, id, data),
528
- delete: (id) => this.delete(slug, id)
145
+ delete: (id) => this.delete(slug, id),
146
+ /**
147
+ * Upload a file to this collection. Sends as multipart/form-data.
148
+ * @param file - A File or Blob (browser) or Buffer with filename/mimeType (Node.js)
149
+ * @param data - Additional metadata fields to save alongside the file (e.g. alt, caption)
150
+ */
151
+ upload: (file, data) => this._upload(slug, file, data),
152
+ // ---- Auth methods (only meaningful when the collection has auth: true) ----
153
+ /**
154
+ * Log in with email + password. Returns a JWT token and the user document.
155
+ * Call `client.setToken(token)` afterwards to authenticate subsequent requests.
156
+ */
157
+ login: (email, password) => this.request(`/api/collections/${slug}/login`, {
158
+ method: "POST",
159
+ body: JSON.stringify({ email, password })
160
+ }),
161
+ /** Log out. Stateless — token must be discarded client-side; call client.clearToken() too. */
162
+ logout: () => this.request(`/api/collections/${slug}/logout`, { method: "POST" }),
163
+ /** Return the currently authenticated user (requires a token via setToken). */
164
+ me: () => this.request(`/api/collections/${slug}/me`),
165
+ /** Issue a fresh token for the currently authenticated user. */
166
+ refreshToken: () => this.request(`/api/collections/${slug}/refresh-token`, { method: "POST" }),
167
+ /** Check if this auth collection has any users (initialized). */
168
+ isInitialized: () => this.request(`/api/collections/${slug}/init`),
169
+ /** Register the very first user in an empty auth collection. */
170
+ registerFirstUser: (data) => this.request(`/api/collections/${slug}/first-user`, {
171
+ method: "POST",
172
+ body: JSON.stringify(data)
173
+ }),
174
+ /** Send an invitation email to a new user. Requires authentication. */
175
+ invite: (email) => this.request(`/api/collections/${slug}/invite`, {
176
+ method: "POST",
177
+ body: JSON.stringify({ email })
178
+ }),
179
+ /** Accept an invitation and create an account. Returns token + user. */
180
+ acceptInvite: (token, password, extraFields) => this.request(`/api/collections/${slug}/accept-invite`, {
181
+ method: "POST",
182
+ body: JSON.stringify({ token, password, ...extraFields })
183
+ })
184
+ };
185
+ }
186
+ /**
187
+ * Access a global by its slug with a fluent builder.
188
+ * @example client.global('site-settings').get()
189
+ * @example client.global('site-settings').update({ siteName: 'My Site' })
190
+ */
191
+ global(slug) {
192
+ return {
193
+ get: (args = {}) => this.getGlobal(slug, args),
194
+ update: (data) => this.updateGlobal(slug, data)
529
195
  };
530
196
  }
531
197
  async findOne(collection, id, args = {}) {
532
- const query = qs.stringify(args, { addQueryPrefix: true });
533
- return this.request(`/api/collections/${collection}/${id}${query}`);
198
+ const { initialData, ...queryArgs } = args;
199
+ const query = qs.stringify(queryArgs, { addQueryPrefix: true });
200
+ try {
201
+ return await this.request(`/api/collections/${collection}/${id}${query}`);
202
+ } catch (err) {
203
+ if (err instanceof DyrectedError && err.statusCode === 404 && initialData) {
204
+ this.request(`/api/collections/${collection}/seed`, {
205
+ method: "POST",
206
+ body: JSON.stringify({ data: [{ id, ...initialData }] })
207
+ }).catch((err2) => console.error(`[dyrected/sdk] Failed to auto-seed document "${id}" in collection "${collection}":`, err2));
208
+ return initialData;
209
+ }
210
+ throw err;
211
+ }
534
212
  }
535
213
  async create(collection, data) {
536
214
  return this.request(`/api/collections/${collection}`, {
@@ -550,8 +228,28 @@ var DyrectedClient = class {
550
228
  });
551
229
  }
552
230
  async getGlobal(slug, args = {}) {
553
- const query = qs.stringify(args, { addQueryPrefix: true });
554
- return this.request(`/api/globals/${slug}${query}`);
231
+ const { initialData, ...queryArgs } = args;
232
+ const query = qs.stringify(queryArgs, { addQueryPrefix: true });
233
+ try {
234
+ const res = await this.request(`/api/globals/${slug}${query}`);
235
+ if ((!res || Object.keys(res).length === 0) && initialData) {
236
+ this.request(`/api/globals/${slug}/seed`, {
237
+ method: "POST",
238
+ body: JSON.stringify({ data: initialData })
239
+ }).catch((err) => console.error(`[dyrected/sdk] Failed to auto-seed global "${slug}":`, err));
240
+ return initialData;
241
+ }
242
+ return res;
243
+ } catch (err) {
244
+ if (err instanceof DyrectedError && err.statusCode === 404 && initialData) {
245
+ this.request(`/api/globals/${slug}/seed`, {
246
+ method: "POST",
247
+ body: JSON.stringify({ data: initialData })
248
+ }).catch((err2) => console.error(`[dyrected/sdk] Failed to auto-seed global "${slug}":`, err2));
249
+ return initialData;
250
+ }
251
+ throw err;
252
+ }
555
253
  }
556
254
  async updateGlobal(slug, data) {
557
255
  return this.request(`/api/globals/${slug}`, {
@@ -559,37 +257,59 @@ var DyrectedClient = class {
559
257
  body: JSON.stringify(data)
560
258
  });
561
259
  }
562
- async listMedia(args = {}) {
563
- const query = qs.stringify(args, { addQueryPrefix: true });
564
- return this.request(`/api/media${query}`);
260
+ async listMedia(args = {}, collection = "media") {
261
+ return this.find(collection, args);
262
+ }
263
+ /** @deprecated Use client.collection('media').upload(file, data) instead */
264
+ async uploadMedia(file, collection = "media") {
265
+ return this._upload(collection, file);
565
266
  }
566
- async uploadMedia(file) {
267
+ /**
268
+ * Internal upload implementation shared by collection().upload() and uploadMedia().
269
+ */
270
+ async _upload(collection, file, data) {
567
271
  const formData = new FormData();
568
272
  formData.append("file", file);
273
+ if (data) {
274
+ for (const [key, value] of Object.entries(data)) {
275
+ formData.append(key, value);
276
+ }
277
+ }
569
278
  const { "Content-Type": _, ...headers } = this.headers;
570
- return this.request("/api/media", {
279
+ return this.request(`/api/collections/${collection}`, {
571
280
  method: "POST",
572
- headers,
281
+ headers: {
282
+ ...headers,
283
+ "Content-Type": void 0
284
+ },
573
285
  body: formData
574
286
  });
575
287
  }
576
- async deleteMedia(id) {
577
- return this.request(`/api/media/${id}`, {
578
- method: "DELETE"
579
- });
288
+ async deleteMedia(id, collection = "media") {
289
+ return this.delete(collection, id);
580
290
  }
581
291
  async request(path, init) {
582
292
  const url = `${this.baseUrl}${path}`;
293
+ const allHeaders = {
294
+ ...this.headers,
295
+ ...init?.headers
296
+ };
297
+ Object.keys(allHeaders).forEach((key) => {
298
+ if (allHeaders[key] === void 0) {
299
+ delete allHeaders[key];
300
+ }
301
+ });
583
302
  const res = await this.fetch(url, {
584
303
  ...init,
585
- headers: {
586
- ...this.headers,
587
- ...init?.headers
588
- }
304
+ headers: allHeaders
589
305
  });
590
306
  if (!res.ok) {
591
- const error = await res.json().catch(() => ({ message: "Unknown error" }));
592
- throw new Error(error.message || `Request failed with status ${res.status}`);
307
+ const body = await res.json().catch(() => ({ message: "Unknown error" }));
308
+ throw new DyrectedError(
309
+ body.message || `Request failed with status ${res.status}`,
310
+ res.status,
311
+ body.errors || []
312
+ );
593
313
  }
594
314
  return res.json();
595
315
  }
@@ -599,5 +319,6 @@ function createClient(config) {
599
319
  }
600
320
  export {
601
321
  DyrectedClient,
322
+ DyrectedError,
602
323
  createClient
603
324
  };